> For the complete documentation index, see [llms.txt](https://intercode.gitbook.io/intercode-saas-kit/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://intercode.gitbook.io/intercode-saas-kit/pages/auth/oauth-providers.md).

# 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 %}
