# OAuth Providers

To search for available Passport strategies use [PassportJS Strategies list](https://www.passportjs.org/packages/).

### Dependencies&#x20;

First of all we need to install libraries needed to implement OAuth provider

```bash
yarn add passport-facebook
```

### Environment Variables

Register your application within your OAuth provider ([facebook as an example](/intercode-saas-kit/external-integrations/facebook-oauth2.md))

* Register your application at [Facebook Developers](https://developers.facebook.com/).
* Set up the callback URL in your Facebook app configuration:

  ```plaintext
  <BASE_URL>/auth/callback
  ```

#### API

{% code title=".env" %}

```
FACEBOOK_APP_ID=your_id
FACEBOOK_APP_SECRET=your_secret
```

{% endcode %}

{% code title="constants.ts" fullWidth="false" %}

```javascript
FACEBOOK_APP_ID: process.env.FACEBOOK_APP_ID,
FACEBOOK_APP_SECRET: process.env.FACEBOOK_APP_SECRET,
```

{% endcode %}

#### UI

{% code title=".env.development" %}

```
REACT_APP_FACEBOOK_CLIENT_ID=your_id
```

{% endcode %}

{% code title="constants.js" fullWidth="false" %}

```javascript
FACEBOOK: { CLIENT_ID: process.env.REACT_APP_FACEBOOK_CLIENT_ID, EMAIL_SCOPE: 'email' },
```

{% endcode %}

### Data Types

Since we're using TypeScript, it's crucial to define appropriate data types to&#x20;

{% code title="open-auth-payload.ts" %}

```typescript
export class OAuthPayload {
    email: string;
    firstName: string;
    lastName: string;
    picture?: string;
    accessToken: string;
    refreshToken?: string;
}

// Using outer layer type to access user data without using <any>
export class PassportResponse {
    user: OAuthPayload;
    // body: oAuth code
    // url: called url
    // statusCode
    // statusMassage
}
```

{% endcode %}

### Strategy Class

`FacebookStrategy` extends `PassportStrategy` from Passport.js and implements the Facebook OAuth login flow.

* **Constructor**:
  * Initializes the Facebook strategy with required options such as `clientID`, `clientSecret`, and `callbackURL`.
  * Logs an error if Facebook credentials (`FACEBOOK_APP_ID` and `FACEBOOK_APP_SECRET`) are not provided in `constants`.
* **Options**:
  * `clientID`: Facebook App ID.
  * `clientSecret`: Facebook App Secret.
  * `callbackURL`: URL that Facebook redirects to after successful authentication.
  * `scope`: Scope of permissions; here, it requests access to the user's email.
  * `profileFields`: Specifies the fields retrieved from the Facebook profile (e.g., `emails`, `name`, `picture`).

***

**`validate` Method**

This method processes the Facebook profile data to create an `OAuthPayload` object and pass it to the `done` callback.

* **Parameters**:
  * `accessToken` & `refreshToken`: Tokens returned by Facebook for accessing user information.
  * `profile`: Facebook profile object containing user information.
  * `done`: Callback to complete the validation process.
* **Data Mapping**:
  * Maps `profile` fields to the `OAuthPayload` fields:
    * `email`: Primary email of the user.
    * `firstName`: User's first name.
    * `lastName`: User's last name.
    * `picture`: Profile picture URL.
* **Error Handling**:
  * If an error occurs, `handleError()` is invoked, returning a `BAD_REQUEST` HTTP error with a user-friendly message.

```typescript
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Profile, Strategy } from 'passport-facebook';
import constants from '../../constants';
import { OAuthPayload, PassportResponse } from '../dto/open-auth-payload';

const handleError = () => {
    throw new HttpException(
        {
            message: 'Authentication Error',
            description: "Sorry, we couldn't sign you in at the moment. Please try again later.",
        },
        HttpStatus.BAD_REQUEST
    );
};

@Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') {
    constructor() {
        const clientID = constants.FACEBOOK_APP_ID;
        const clientSecret = constants.FACEBOOK_APP_SECRET;

        if (!clientID || !clientSecret) {
            console.error('[ERROR] Please set up FACEBOOK_APP_ID and FACEBOOK_APP_SECRET');
        }

        super({
            clientID: clientID,
            clientSecret: clientSecret,
            callbackURL: `${constants.BASE_URL}/auth/callback`,
            scope: 'email',
            profileFields: ['emails', 'name', 'picture'],
        });
    }

    async validate(
        accessToken: string,
        refreshToken: string,
        profile: Profile,
        done: (err: Error, user: OAuthPayload) => void
    ): Promise<PassportResponse> {
        try {
            const { name, emails, photos } = profile;

            const user: OAuthPayload = {
                email: emails[0].value,
                firstName: name.givenName,
                lastName: name.familyName,
                picture: photos[0].value,
                accessToken,
                refreshToken,
            };

            // Passing OAuthPayload into user field of a response
            done(null, user);
        } catch (error) {
            done(handleError(), null);

            return Promise.reject(error);
        }
    }
}
```

After creating strategy we need to register it within a module

```typescript
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { FacebookStrategy } from './strategies/facebook.strategy';

@Module({
    imports: [PassportModule],
    providers: [FacebookStrategy],
    exports: [PassportModule],
})
export class AuthModule {}
```

### API Usage

Create endpoints in an `AuthController` to start the Facebook OAuth flow.

```typescript
import { Controller, Post, Req, Res, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { PassportResponse } from '../dto/open-auth-payload';

@Controller('auth')
export class AuthController {
    @Post('facebook/sign-in')
    @UseGuards(FacebookAuthGuard)
    async facebookSignIn(@Req() payload: PassportResponse): Promise<UserTokens> {
        return await this.authService.facebookAuth(payload.user);
    }

    @Post('facebook/sign-up')
    @UseGuards(FacebookAuthGuard)
    async facebookSignUp(@Req() payload: PassportResponse): Promise<UserTokens> {
        return await this.authService.facebookAuth(payload.user, true);
    }
}
```

### OAuth Button

The component renders a `Button` styled for Facebook sign-in. When clicked, it begins the Facebook login process, handling responses and redirections based on authentication status.

* **State and Hooks**:
  * `useState` for `loading`: Indicates when the login request is being processed.
  * `useAuth`, `useAuthService`, and `useNavigate`: Contexts and services for handling authentication state and navigation.
  * `useEffect` and `useCallback` manage the popup's event listeners and message handling.
* **URL Construction** (`getCode` function):
  * Constructs the Facebook OAuth URL using the client ID and redirect URI.
  * Initiates the OAuth flow by calling `openSignInWindow`.
* **Popup Handling** (`openSignInWindow` and `receiveMessage` functions):
  * `openSignInWindow` creates the popup for Facebook’s OAuth and sets up a message listener.
  * `receiveMessage` processes the OAuth response:
    * Checks the event origin for security.
    * Parses the `code` parameter returned by Facebook.
    * Calls the `facebookAuth` function with the `code` to retrieve the user’s tokens.
* **Authentication and Navigation Logic**:
  * After receiving the tokens, the `accessToken`, `refreshToken`, and `isTwoFactorEnable` are evaluated:
    * If 2FA is enabled, redirects to the 2FA setup route.
    * Otherwise, stores tokens and navigates to the root path.
* **Error Handling**:
  * Displays an error notification if there’s an issue during authentication.
  * Switches the active tab to the sign-up screen if necessary.

```javascript
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from 'antd';
import { useAuthService } from '../../services/authService';
import { useAuth } from '../../context/AuthContext';
import { routesPath } from '../../common/constants/routesPath';
import { notificationType } from '../../common/constants/notificationType';
import { constants } from '../../common/constants/constants';
import { textColor, spacing, textStandard } from '../../theming';
import facebook from '../../assets/Facebook.png';

const FacebookSignInButton = ({ authType, setActiveTabKey, openNotification, t }) => {
    let windowObjectReference = null;
    let previousUrl = null;
    let path = authType.toLowerCase();

    useEffect(() => {
        return () => {
            window.removeEventListener('message', receiveMessage);
        };
    }, []);

    const [loading, setLoading] = useState(false);
    const { setAuth } = useAuth();
    const navigate = useNavigate();
    const { facebookAuth } = useAuthService();

    function encodeQueryData(data) {
        const ret = [];
        for (let d in data) {
            ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
        }

        return ret.join('&');
    }

    const getCode = () => {
        const queryData = {
            scope: 'email',
            response_type: 'code',
            redirect_uri: `${constants.BASE_URL}/auth/callback`,
            client_id: constants.FACEBOOK.CLIENT_ID,
        };
        const queryString = encodeQueryData(queryData);

        const url = `https://www.facebook.com/v19.0/dialog/oauth?${queryString}`;

        openSignInWindow(url, 'facebook-redirect');
    };

    const openSignInWindow = (url, name) => {
        // Remove any existing event listeners
        window.removeEventListener('message', receiveMessage, false);

        // Window features
        const strWindowFeatures = 'toolbar=no, menubar=no, width=600, height=700, top=100, left=100';

        if (windowObjectReference === null || windowObjectReference.closed) {
            /* If the pointer to the window object in memory does not exist
             or if such pointer exists but the window was closed */
            windowObjectReference = window.open(url, name, strWindowFeatures);
        } else if (previousUrl !== url) {
            /* If the resource to load is different,
             then we load it in the already opened secondary window, and then
             we bring such window back on top/in front of its parent window. */
            windowObjectReference = window.open(url, name, strWindowFeatures);
            windowObjectReference.focus();
        } else {
            /* Else the window reference must exist and the window
             is not closed; therefore, we can bring it back on top of any other
             window with the focus() method. There would be no need to re-create
             the window or to reload the referenced resource. */
            windowObjectReference.focus();
        }

        // Add the listener for receiving a message from the popup
        window.addEventListener('message', receiveMessage, false);
        // Assign the previous URL
        previousUrl = url;
    };

    const receiveMessage = useCallback(
        async event => {
            // Check if we could trust the event origin
            if (event.origin !== constants.BASE_URL) {
                return;
            }

            const { data } = event;
            // Check if the source is our popup
            if (event.source.name === 'facebook-redirect') {
                window.removeEventListener('message', receiveMessage, false);

                setLoading(true);
                let params = new URLSearchParams(data);
                const code = params.get('code');

                try {
                    const userTokens = await facebookAuth(path, code);

                    if (userTokens?.data) {
                        const { accessToken, refreshToken, isTwoFactorEnable } = userTokens.data;

                        if (isTwoFactorEnable) {
                            setAuth(accessToken, null);
                            navigate(`${routesPath.AUTH.PATH}/${routesPath.AUTH.TWO_FACTOR_AUTH}`);
                        } else {
                            setAuth(accessToken, refreshToken);
                            navigate(routesPath.ROOT.PATH);
                        }
                    }

                    setLoading(false);
                } catch (error) {
                    openNotification({
                        type: notificationType.ERROR,
                        message: error.response?.data?.message ?? error.response.status,
                        description: error.response?.data?.description ?? error.response.statusText,
                    });
                    setLoading(false);
                    setActiveTabKey(error.response.data?.redirectToSignUp ? '1' : '0');
                    navigate('/auth/sign-in');
                }
                // Remove any existing event listeners to prevent multiple requests
                window.removeEventListener('message', receiveMessage, false);
            }
        },
        [authType]
    );

    return (
        <Button
            loading={loading}
            onClick={getCode}
            style={{
                background: 'inherit',
                width: '100%',
                color: textColor.secondaryAlt,
                ...textStandard,
                fontWeight: 500,
            }}
            icon={<img src={facebook} alt="google_icon" style={{ marginRight: spacing[1] }} />}
            size="large"
        >
            {t('auth:buttons.facebook')}
        </Button>
    );
};

export default FacebookSignInButton;
```

### OAuth Callback

This component should be set as the `callbackURL` route in your app’s router configuration. After Facebook redirects to this route, the component completes the OAuth handshake by communicating the response back to the original window and closing itself

* **`useEffect` Hook:**
  * When the component mounts, it retrieves the URL parameters (`window.location.search`), which includes the OAuth code sent by Facebook after the user authorizes the app.
* **Message Posting**:
  * It checks if the current window has an `opener` (the original window that opened the popup).
  * If an `opener` exists, it sends the URL parameters back to the opener using `window.opener.postMessage(params)`. The original window then handles these parameters (e.g., by extracting the authorization code).
* **Popup Closure**:
  * After posting the message, `window.close()` is called to close the popup, finishing the OAuth flow.

<pre class="language-javascript"><code class="lang-javascript"><strong>import { useEffect } from 'react';
</strong>
function Callback() {
    useEffect(() => {
        // Get the URL parameters which will include the code
        const params = window.location.search;
        if (window.opener) {
            // Send them to the opening window
            window.opener.postMessage(params);
            // Close the popup
            window.close();
        }
    }, []);

    return &#x3C;p>Please wait...&#x3C;/p>;
}

export default Callback;
</code></pre>

### Sequence Diagram

This OAuth flow diagram helps provide a clear visual representation of how authentication works between the UI, API, and OAuth provider.

<figure><img src="/files/jA0TDiLcl8iJjcRXr8x8" alt=""><figcaption><p>OAuth flow sequence diagram</p></figcaption></figure>

***

{% content-ref url="/pages/o9xNoaGdNsCfbbTiD2OI" %}
[Google oAuth2](/intercode-saas-kit/external-integrations/google-oauth2.md)
{% endcontent-ref %}

{% content-ref url="/pages/NrUb92erjzeRNUcgJ9n4" %}
[Facebook oAuth2](/intercode-saas-kit/external-integrations/facebook-oauth2.md)
{% endcontent-ref %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://intercode.gitbook.io/intercode-saas-kit/pages/auth/oauth-providers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
