@fastify/oauth2

repository·main·Indexed 43 days ago

https://github.com/fastify/fastify-oauth2

A Fastify plugin that provides a wrapper around the simple-oauth2 library to simplify implementing OAuth2 authentication flows. It supports PKCE, OpenID Connect discovery, and includes preset configurations for providers like Google, Facebook, GitHub, and Microsoft. Version 8.3.0 requires @fastify/cookie for secure exchange.

Tokens
6.5K
Snippets
21
Records
25
Agent score
6%

What's inside fastify-oauth2

  1. Access the underlying simple-oauth2 instance via namespaces

    main

    The plugin decorates the Fastify instance with the simple-oauth2 instance. To allow multiple OAuth providers to coexist without collision, the instance is placed inside a namespace defined by the name property in your configuration.

    The instance is accessible via two paths:

    1. fastify.oauth2<name>.oauth2
    2. fastify.<name>.oauth2
    // If registered with name: 'facebook'
    // Access via:
    fastify.oauth2Facebook.oauth2
    fastify.facebook.oauth2
  2. Use hostPrefixedCookies for enhanced security

    main

    Setting hostPrefixedCookies: true switches default cookie names to their __Host- prefixed forms (__Host-oauth2-redirect-state and __Host-oauth2-code-verifier).

    Why use this? It protects against Login CSRF by ensuring cookies can only be written by a secure origin and are scoped to the host only. This is highly recommended if any host under your registrable domain is outside your control.

    Requirements:

    • The application must be reached via HTTPS (browsers enforce this for __Host- cookies).
    • Local development on http://localhost still works as browsers treat it as a secure context.
    • If you use an explicit redirectStateCookieName or verifierCookieName, those will override the prefixing.
    fastify.register(oauthPlugin, {
      ...,
      hostPrefixedCookies: true
    })
  3. Configure TypeScript for @fastify/oauth2

    main

    To get type-safe access to your OAuth2 decorators, use TypeScript declaration merging. The plugin applies decorations to your FastifyInstance based on the name provided in your configuration.

    Ensure you have @types/node installed.

    import { OAuth2Namespace } from '@fastify/oauth2';
    
    declare module 'fastify' {
      interface FastifyInstance {
        facebookOAuth2: OAuth2Namespace;
        myCustomOAuth2: OAuth2Namespace;
      }
    }
  4. Handle provider quirks for Twitch and LinkedIn

    main

    Some providers require client_id and client_secret to be explicitly included in the body of the token request. For Twitch and LinkedIn, you must pass these via the tokenRequestParams option.

    Example for Twitch:

    fastify.register(oauthPlugin, {
      name: 'twitchOauth2',
      credentials: {
        client: {
          id: '<CLIENT_ID>',
          secret: '<CLIENT_SECRET'
        },
        auth: oauthPlugin.TWITCH_CONFIGURATION
      },
      tokenRequestParams: {
        client_id: '<CLIENT_ID>',
        client_secret: '<CLIENT_SECRET>',
      },
      startRedirectPath: '/login/twitch',
      callbackUri: 'http://localhost:3000/login/twitch/callback'
    })
  5. Implement the OAuth2 login flow

    main

    To use @fastify/oauth2, you must create two endpoints: one to initiate the redirect to the OAuth2 provider and one to handle the callback from the provider.

    1. Initialization Endpoint: Triggers the redirect. If you provide startRedirectPath in the plugin configuration, this endpoint is handled automatically. Otherwise, you must manually call generateAuthorizationUri on the decorated plugin instance.
    2. Callback Endpoint: The URL where the service provider redirects the user after successful authentication. Use getAccessTokenFromAuthorizationCodeFlow to retrieve the access token.
    const fastify = require('fastify')({ logger: { level: 'trace' } })
    const oauthPlugin = require('@fastify/oauth2')
    
    fastify.register(oauthPlugin, {
      name: 'facebookOAuth2',
      credentials: {
        client: {
          id: '<CLIENT_ID>',
          secret: '<CLIENT_SECRET'
        },
        auth: oauthPlugin.FACEBOOK_CONFIGURATION
      },
      startRedirectPath: '/login/facebook',
      callbackUri: 'http://localhost:3000/login/facebook/callback'
    })
    
    // 1. Initialization endpoint (if startRedirectPath is not used)
    fastify.get('/login/facebook', {}, (req, reply) => {
      fastify.facebookOAuth2.generateAuthorizationUri(
        req,
        reply,
        (err, authorizationEndpoint) => {
         if (err) console.error(err)
         reply.redirect(authorizationEndpoint)
        }
      );
    });
    
    // 2. Callback endpoint
    fastify.get('/login/facebook/callback', async function (request, reply) {
      const { token } = await this.facebookOAuth2.getAccessTokenFromAuthorizationCodeFlow(request)
      reply.send({ access_token: token.access_token나})
    })
  6. Configure @fastify/oauth2 with @fastify/cookie

    main

    Since v7.2.0, @fastify/oauth2 requires cookies for secure exchange. You must register @fastify/cookie before @fastify/oauth2.

    By default, cookies are httpOnly and sameSite: 'Lax'. You can customize cookie settings, names, and signing via the configuration object.

    const fastify = require('fastify')({ logger: { level: 'trace' } })
    const oauthPlugin = require('@fastify/oauth2')
    
    // Register cookie plugin FIRST
    fastify.register(require('@fastify/cookie'), cookieOptions)
    // Then register oauth plugin
    fastify.register(oauthPlugin, oauthOptions)
  7. Configure custom OAuth2 endpoints

    main

    If a preset is not available, you can manually define the OAuth2 endpoints in the auth object:

    • authorizeHost: The base URL for authorization.
    • authorizePath: The path to the authorization endpoint.
    • tokenHost: The base URL for the token service.
    • tokenPath: The path to the token endpoint.

    You can also pass additional parameters to the callback via callbackUriParams.

    fastify.register(oauthPlugin, {
      name: 'customOauth2',
      credentials: {
        client: {
          id: '<CLIENT_ID>',
          secret: '<CLIENT_SECRET'
        },
        auth: {
          authorizeHost: 'https://my-site.com',
          authorizePath: '/authorize',
          tokenHost: 'https://token.my-site.com',
          tokenPath: '/api/token'
        }
      },
      startRedirectPath: '/login',
      callbackUri: 'http://localhost:3000/login/callback',
      callbackUriParams: {
        exampleParam: 'example param value'
      }
    })
  8. Configure schema for the startRedirectPath endpoint

    main

    You can provide a schema object to define the JSON schema for the startRedirectPath endpoint. This is particularly useful when using @fastify/swagger to generate documentation.

    Note: The schema option overrides the tags option entirely; it does not merge them.

    fastify.register(oauthPlugin, {
      name: 'facebookOAuth2',
      credentials: {
        client: {
          id: '<CLIENT_ID>',
          secret: '<CLIENT_SECRET>'
        },
        auth: oauthPlugin.FACEBOOK_CONFIGURATION
      },
      startRedirectPath: '/login/facebook',
      callbackUri: 'http://localhost:3000/login/facebook/callback',
      tags: ['facebook', 'oauth2'],
      schema: {
        tags: ['facebook', 'oauth2'] // this will take the precedence
      }
    })
  9. Set custom callbackUri query parameters

    main

    Use the callbackUriParams option to pass additional query parameters to the OAuth provider during the callback phase. This is useful for provider-specific requirements, such as requesting offline access in Google.

    fastify.register(oauthPlugin, {
      name: 'googleOAuth2',
      scope: ['profile', 'email'],
      credentials: {
        client: {
          id: '<CLIENT_ID>',
          secret: '<CLIENT_SECRET>',
        },
        auth: oauthPlugin.GOOGLE_CONFIGURATION,
      },
      startRedirectPath: '/login/google',
      callbackUri: 'http://localhost:3000/login/google/callback',
      callbackUriParams: {
        access_type: 'offline', // will tell Google to send a refreshToken too
      },
      pkce: 'S256'
    });
  10. Use automated discovery endpoint with OpenID Connect

    main

    If your provider supports OpenID Connect discovery, you can automatically configure authorization, token, and revocation endpoints using the discovery option. This option requires an issuer property, which can be a string URL or a metadata URL (supporting both trailing and non-trailing slashes).

    When using discovery:

    • You do not need to set credentials.auth.
    • The plugin will automatically attempt to select the appropriate PKCE method for the authorization code grant. You can still set pkce explicitly if you have a specific requirement.
    fastify.register(oauthPlugin, {
      name: 'customOAuth2',
      scope: ['profile', 'email'],
      credentials: {
        client: {
          id: '<CLIENT_ID>',
          secret: '<CLIENT_SECRET>',
        },
        // Note how "auth" is not needed anymore when discovery is used.
      },
      startRedirectPath: '/login',
      callbackUri: 'http://localhost:3000/callback',
      discovery: { issuer: 'https://identity.mycustomdomain.com' }
    });