Code Monkey home page Code Monkey logo

fastify-oauth2's Introduction

@fastify/oauth2

CI NPM version js-standard-style

Wrapper around the simple-oauth2 library.

v4.x of this module support Fastify v3.x v3.x of this module support Fastify v2.x

Install

npm i @fastify/oauth2

Usage

Two separate endpoints need to be created when using the fastify-oauth2 module, one for the callback from the OAuth2 service provider (such as Facebook or Discord) and another for initializing the OAuth2 login flow.

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
  },
  // register a fastify url to start the redirect flow to the service provider's OAuth2 login
  startRedirectPath: '/login/facebook',
  // service provider redirects here after user login
  callbackUri: 'http://localhost:3000/login/facebook/callback'
  // You can also define callbackUri as a function that takes a FastifyRequest and returns a string
  // callbackUri: req => `${req.protocol}://${req.hostname}/login/facebook/callback`,
})

// This is the new endpoint that initializes the OAuth2 login flow
// This endpoint is only required if startRedirectPath has not been provided
fastify.get('/login/facebook', {}, (req, reply) => {
  fastify.facebookOAuth2.generateAuthorizationUri(
    req,
    reply,
    (err, authorizationEndpoint) => {
     if (err) console.error(err)
     reply.redirect(authorizationEndpoint)
    }
  );
});

// The service provider redirect the user here after successful login
fastify.get('/login/facebook/callback', async function (request, reply) {
  const { token } = await this.facebookOAuth2.getAccessTokenFromAuthorizationCodeFlow(request)
  
  console.log(token.access_token)

  // if later need to refresh the token this can be used
  // const { token: newToken } = await this.getNewAccessTokenUsingRefreshToken(token)

  reply.send({ access_token: token.access_token })
})

In short, it is necessary to initially navigate to the /login/facebook endpoint manually in a web browser. This will redirect to the OAuth2 service provider's login screen. From there, the service provider will automatically redirect back to the /login/facebook/callback endpoint where the access token can be retrieved and used. The CLIENT_ID and CLIENT_SECRET need to be replaced with the ones provided by the service provider.

A complete example is provided at fastify-discord-oauth2-example

Usage with @fastify/cookie

Since v7.2.0, @fastify/oauth2 requires the use of cookies to securely implement the OAuth2 exchange. Therefore, if you need @fastify/cookie yourself, you will need to register it before @fastify/oauth2.

const fastify = require('fastify')({ logger: { level: 'trace' } })
const oauthPlugin = require('@fastify/oauth2')

fastify.register(require('@fastify/cookie'), cookieOptions)
fastify.register(oauthPlugin, oauthOptions)

Cookies are by default httpOnly, sameSite: Lax. If this does not suit your use case, it is possible to override the default cookie settings by providing options in the configuration object, for example

fastify.register(oauthPlugin, {
  ...,
  cookie: {
    secure: true,
    sameSite: 'none'
  }
})

Preset configurations

You can choose some default setup to assign to auth option.

  • APPLE_CONFIGURATION
  • FACEBOOK_CONFIGURATION
  • GITHUB_CONFIGURATION
  • GITLAB_CONFIGURATION
  • LINKEDIN_CONFIGURATION
  • GOOGLE_CONFIGURATION
  • MICROSOFT_CONFIGURATION
  • VKONTAKTE_CONFIGURATION
  • SPOTIFY_CONFIGURATION
  • DISCORD_CONFIGURATION
  • TWITCH_CONFIGURATION
  • VATSIM_CONFIGURATION
  • VATSIM_DEV_CONFIGURATION
  • EPIC_GAMES_CONFIGURATION
  • YANDEX_CONFIGURATION

Custom configuration

Of course, you can set the OAUTH endpoints by yourself if a preset is not in our module:

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'
  }
})

Use automated discovery endpoint

When your provider supports OpenID connect discovery and you want to configure authorization, token and revocation endpoints automatically, then you can use discovery option. discovery is a simple object that requires issuer property.

Issuer is expected to be string URL or metadata url. Variants with or without trailing slash are supported.

You can see more in example here.

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' }
  // pkce: 'S256', you can still do this explicitly, but since discovery is used,
  // it's BEST to let plugin do it itself 
  // based on what Authorization Server Metadata response
});

Important notes for discovery:

  • You should not set up credentials.auth anymore when discovery mechanics is used.
  • When your provider supports it, plugin will also select appropriate PKCE method in authorization code grant
  • In case you still want to select method yourself, and know exactly what you are doing; you can still do it explicitly.

Schema configuration

You can specify your own schema for the startRedirectPath end-point. It allows you to create a well-documented document when using @fastify/swagger together. Note: schema option will override the tags option without merging them.

fastify.register(oauthPlugin, {
  name: 'facebookOAuth2',
  credentials: {
    client: {
      id: '<CLIENT_ID>',
      secret: '<CLIENT_SECRET>'
    },
    auth: oauthPlugin.FACEBOOK_CONFIGURATION
  },
  // register a fastify url to start the redirect flow
  startRedirectPath: '/login/facebook',
  // facebook redirect here after the user login
  callbackUri: 'http://localhost:3000/login/facebook/callback',
  // add tags for the schema
  tags: ['facebook', 'oauth2'],
  // add schema
  schema: {
    tags: ['facebook', 'oauth2'] // this will take the precedence
  }
})

Set custom state

The generateStateFunction accepts a function to generate the state parameter for the OAUTH flow. This function receives the Fastify instance's request object as parameter. The state parameter will be also set into a httpOnly, sameSite: Lax cookie. When you set it, it is required to provide the function checkStateFunction in order to validate the states generated.

  fastify.register(oauthPlugin, {
    name: 'facebookOAuth2',
    credentials: {
      client: {
        id: '<CLIENT_ID>',
        secret: '<CLIENT_SECRET>'
      },
      auth: oauthPlugin.FACEBOOK_CONFIGURATION
    },
    // register a fastify url to start the redirect flow
    startRedirectPath: '/login/facebook',
    // facebook redirect here after the user login
    callbackUri: 'http://localhost:3000/login/facebook/callback',
    // custom function to generate the state
    generateStateFunction: (request) => {
      const state = request.query.customCode
      request.session.state = state
      return state
    },
    // custom function to check the state is valid
    checkStateFunction: (request, callback) => {
      if (request.query.state === request.session.state) {
        callback()
        return
      }
      callback(new Error('Invalid state'))
    }
  })

Async functions are supported here, and the fastify instance can be accessed via this.

  fastify.register(oauthPlugin, {
    name: 'facebookOAuth2',
    credentials: {
      client: {
        id: '<CLIENT_ID>',
        secret: '<CLIENT_SECRET>'
      },
      auth: oauthPlugin.FACEBOOK_CONFIGURATION
    },
    // register a fastify url to start the redirect flow
    startRedirectPath: '/login/facebook',
    // facebook redirect here after the user login
    callbackUri: 'http://localhost:3000/login/facebook/callback',
    // custom function to generate the state and store it into the redis
    generateStateFunction: async function (request) {
      const state = request.query.customCode
      await this.redis.set(stateKey, state)
      return state
    },
    // custom function to check the state is valid
    checkStateFunction: async function (request, callback) {
      if (request.query.state !== request.session.state) {
        throw new Error('Invalid state')
      }
      return true
    }
  })

Set custom callbackUri Parameters

The callbackUriParams accepts an object that will be translated to query parameters for the callback OAUTH flow. The default value is {}.

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: {
    // custom query param that will be passed to callbackUri
    access_type: 'offline', // will tell Google to send a refreshToken too
  },
  pkce: 'S256'
  // check if your provider supports PKCE, 
  // in case they do, 
  // use of this parameter is highly encouraged 
  // in order to prevent authorization code interception attacks
});

Set custom tokenRequest body Parameters

The tokenRequestParams parameter accepts an object that will be translated to additional parameters in the POST body when requesting access tokens via the service’s token endpoint.

Examples

See the example/ folder for more examples.

Reference

This Fastify plugin decorates the fastify instance with the simple-oauth2 instance inside a namespace specified by the property name both with and without an oauth2 prefix.

E.g. For name: 'customOauth2', the simple-oauth2 instance will become accessible like this:

fastify.oauth2CustomOauth2.oauth2 and fastify.customOauth2.oauth2

In this manner we are able to register multiple OAuth providers and each OAuth providers simple-oauth2 instance will live in it's own namespace.

E.g.

  • fastify.oauth2Facebook.oauth2
  • fastify.oauth2Github.oauth2
  • fastify.oauth2Spotify.oauth2
  • fastify.oauth2Vkontakte.oauth2

Assuming we have registered multiple OAuth providers like this:

  • fastify.register(oauthPlugin, { name: 'facebook', { ... } // facebooks credentials, startRedirectPath, callbackUri etc )
  • fastify.register(oauthPlugin, { name: 'github', { ... } // githubs credentials, startRedirectPath, callbackUri etc )
  • fastify.register(oauthPlugin, { name: 'spotify', { ... } // spotifys credentials, startRedirectPath, callbackUri etc )
  • fastify.register(oauthPlugin, { name: 'vkontakte', { ... } // vkontaktes credentials, startRedirectPath, callbackUri etc )

Utilities

This fastify plugin adds 6 utility decorators to your fastify instance using the same namespace:

  • getAccessTokenFromAuthorizationCodeFlow(request, callback): A function that uses the Authorization code flow to fetch an OAuth2 token using the data in the last request of the flow. If the callback is not passed it will return a promise. The callback call or promise resolution returns an AccessToken object, which has an AccessToken.token property with the following keys:

    • access_token
    • refresh_token (optional, only if the offline scope was originally requested, as seen in the callbackUriParams example)
    • token_type (generally 'Bearer')
    • expires_in (number of seconds for the token to expire, e.g. 240000)
  • OR getAccessTokenFromAuthorizationCodeFlow(request, reply, callback) variant with 3 arguments, which should be used when PKCE extension is used. This allows fastify-oauth2 to delete PKCE code_verifier cookie so it doesn't stay in browser in case server has issue when fetching token. See Google With PKCE example for more.

    Important to note: if your provider supports S256 as code_challenge_method, always prefer that. Only use plain when your provider doesn't support S256.

  • getNewAccessTokenUsingRefreshToken(Token, params, callback): A function that takes a AccessToken-Object as Token and retrieves a new AccessToken-Object. This is generally useful with background processing workers to re-issue a new AccessToken when the previous AccessToken has expired. The params argument is optional and it is an object that can be used to pass in additional parameters to the refresh request (e.g. a stricter set of scopes). If the callback is not passed this function will return a Promise. The object resulting from the callback call or the resolved Promise is a new AccessToken object (see above). Example of how you would use it for name:googleOAuth2:

fastify.googleOAuth2.getNewAccessTokenUsingRefreshToken(currentAccessToken, (err, newAccessToken) => {
   // Handle the new accessToken
});
  • generateAuthorizationUri(requestObject, replyObject, callback): A function that generates the authorization uri. If the callback is not passed this function will return a Promise. The string resulting from the callback call or the resolved Promise is the authorization uri. This is generally useful when you want to handle the redirect yourself in a specific route. The requestObject argument passes the request object to the generateStateFunction). You do not need to declare a startRedirectPath if you use this approach. Example of how you would use it:
fastify.get('/external', { /* Hooks can be used here */ }, (req, reply) => {
  fastify.oauth2CustomOAuth2.generateAuthorizationUri(req, reply, (err, authorizationEndpoint) => {
    reply.redirect(authorizationEndpoint)
  });
});
  • revokeToken(Token, tokenType, params, callback): A function to revoke the current access_token or refresh_token on the authorization server. If the callback is not passed it will return a promise. The callback call or promise resolution returns void
fastify.googleOAuth2.revokeToken(currentAccessToken, 'access_token', undefined, (err) => {
   // Handle the reply here
});
  • revokeAllToken(Token, params, callback): A function to revoke the current access_token and refresh_token on the authorization server. If the callback is not passed it will return a promise. The callback call or promise resolution returns void
fastify.googleOAuth2.revokeAllToken(currentAccessToken, undefined, (err) => {
   // Handle the reply here
});
  • userinfo(tokenOrTokenSet): A function to retrieve userinfo data from Authorization Provider. Both token (as object) or access_token string value can be passed.

Important note: Userinfo will only work when discovery option is used and such endpoint is advertised by identity provider.

For a statically configured plugin, you need to make a HTTP call yourself.

See more on OIDC standard definition for Userinfo endpoint

See more on userinfo_endpoint property in OIDC Discovery Metadata standard definition.

fastify.googleOAuth2.userinfo(currentAccessToken, (err, userinfo) => {
   // do something with userinfo
});
// with custom params
fastify.googleOAuth2.userinfo(currentAccessToken, { method: 'GET', params: { /* add your custom key value pairs here to be appended to request */ } },  (err, userinfo) => {
   // do something with userinfo
});

// or promise version
const userinfo = await fastify.googleOAuth2.userinfo(currentAccessToken);
// use custom params
const userinfo = await fastify.googleOAuth2.userinfo(currentAccessToken, { method: 'GET', params: { /* ... */ } });

There are variants with callback and promises. Custom parameters can be passed as option. See Types and usage patterns in examples.

Note:

We support HTTP GET and POST requests to userinfo endpoint sending access token using Bearer schema in headers. You can do this by setting (via: "header" parameter), but it's not mandatory since it's a default value.

We also support POST by sending access_token in a request body. You can do this by explicitly providing via: "body" parameter.

E.g. For name: 'customOauth2', the helpers getAccessTokenFromAuthorizationCodeFlow and getNewAccessTokenUsingRefreshToken will become accessible like this:

  • fastify.oauth2CustomOauth2.getAccessTokenFromAuthorizationCodeFlow
  • fastify.oauth2CustomOauth2.getNewAccessTokenUsingRefreshToken

Usage with TypeScript

Type definitions are provided with the package. Decorations are applied during runtime and are based on auth configuration name. One solution is to leverage TypeScript declaration merging to add type-safe namespace. Make sure you have @types/node installed for this to work correctly.

In project declarations files .d.ts

import { OAuth2Namespace } from '@fastify/oauth2';

declare module 'fastify' {
  interface FastifyInstance {
    facebookOAuth2: OAuth2Namespace;
    myCustomOAuth2: OAuth2Namespace;
  }
}

All auth configurations are made available with an oauth2 prefix that's typed to OAuth2Namespace | undefined, such as eg. fastify.oauth2CustomOauth2 for customOauth2.

Provider Quirks

The following providers require additional work to be set up correctly.

Twitch

Twitch requires that the request for a token in the oauth2 flow contains the client_id and client_secret properties in tokenRequestParams:

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>',
  },
  // register a fastify url to start the redirect flow
  startRedirectPath: '/login/twitch',
  // twitch redirect here after the user login
  callbackUri: 'http://localhost:3000/login/twitch/callback'
})

License

Licensed under MIT.

NB See simple-oauth2 license too

fastify-oauth2's People

Contributors

aecorredor avatar allevo avatar atinux avatar big-kahuna-burger avatar climba03003 avatar code-raisan avatar dependabot-preview[bot] avatar dependabot[bot] avatar eomm avatar fdawgs avatar frikille avatar frixaco avatar jorisdugue avatar jsumners avatar kamikazechaser avatar lknsi avatar lmammino avatar mcollina avatar mfpopa avatar obscuritysrl avatar pawel-id avatar philsch avatar rodrigodornelles avatar rojandahal avatar salmanm avatar shaunlwm avatar stevesweets avatar tabmk avatar uzlopak avatar voxpelli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastify-oauth2's Issues

[TypeScript] Cannot read property 'googleOAuth2' of undefined

How do I use this plugin in TypeScript? Is this correct?

   fastify.register(oauthPlugin, {
      name: "googleOAuth2",
      scope: ["profile"],
      credentials: {
        client: {
          id: "{ID}",
          secret: "{SECRET}",
        },
        auth: oauthPlugin.GOOGLE_CONFIGURATION,
      },
      startRedirectPath: "/login/google",
      callbackUri: "http://localhost:8080/login/google/callback",
    })

   fastify.get("/login/google/callback", async (req, reply) => {
      try {
        const token = await this.googleOAuth2.getAccessTokenFromAuthorizationCodeFlow
          .getAccessTokenFromAuthorizationCodeFlow(req);

        console.log(token.access_token);

        // if later you need to refresh the token you can use
        // const newToken = await this.getNewAccessTokenUsingRefreshToken(token.refresh_token)

        reply.send({ access_token: token.access_token });
      } catch (error) {
        console.log(error)
      }

Result

TypeError: Cannot read property 'googleOAuth2' of undefined

Your Environment

  • node version: 10
  • fastify version: >=3.0.0
  • os: Linux

[Apple Auth] Redirect by Authorization URL fails without a required query param "response_mode: 'form_post'"

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.2.3

Plugin version

3.0.0

Node.js version

12.19.0

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

10.15.7

Description

A redirect to a generated apple's authorization URL fails with a message invalid_request. response_mode must be form_post when name or email scope is requested when a query parameter called response_mode: 'form_post' is not used in conjunction with another query parameter called scope with a value of either name, or email. See error message below

image

Steps to Reproduce

  1. Define scope as ['name email'] in Apple Fastify OAuth2 config.
  2. Setup all other required configurations for Fastify OAuth2 library. See example below:
export const appleOAuth2Options: FastifyOAuth2Options = {
   name: 'appleOAuth2',
   scope: ['email name'],
   credentials: {
      client: {
        id: APPLE_CLIENT_ID,
        secret: APPLE_SECRET,
      },
      auth: oauthPlugin.APPLE_CONFIGURATION,
   },
   startRedirectPath: '<START_REDIRECT_PATH>',
   callbackUri: `<CALLBACK_URL>`,
};
  1. Wire configuration from Step 2 with your backend application
  2. Open a browser and specify a startRedirectPath so that Fastify can track this endpoint, generate Authorization URL for Apple and redirect you by this URL in the browser.

Actual behavior: Error message invalid_request. response_mode must be form_post when name or email scope is requested appears and authorization fails

Expected Behavior

Apple specs requires to specify an obligated query parameter called response_mode: 'form_post'. See this link for reference. Thus you have to modify a ``

Your current implementation looks like this (see code for reference):
image.

It has to be rewritten like this:

function generateAuthorizationUri (requestObject) {
    const state = generateStateFunction(requestObject)
    const scopeName = Array.isArray(scope) ? scope.join(' ') : scope;
    const hasResponseMode = scopeName.includes('email') || scopeName.includes('name');
    const urlOptions = Object.assign({}, callbackUriParams, {
      redirect_uri: callbackUri,
      scope: scope,
      state: state,
      ...(hasResponseMode && { response_mode: 'form_post' }),
    })
    const authorizationUri = oauth2.authorizationCode.authorizeURL(urlOptions)
    return authorizationUri
  }

image

Apple OAuth2 flow bugged

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.8.1

Plugin version

6.1.0

Node.js version

16.14

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

12.3.1

Description

Hello,

I am facing this issue since previous version of @fastify/oauth2: the authorizationMethod for Apple OAuth2 login is wrongly set to header, leading any login attempt to fail, instead the good format is classic form-body.

What I could do until the previous versions of the plugin was the following workaround code, that I was executing right after the plugin registration:

type OAuth2ReservedNamespace = {
      oauth2: {
         authorizationCode: {
            config: {
               options: {
                  authorizationMethod?: string;
               };
            };
         };
      };
   };

   // We need to delete Auth method, cause it has been wrongly set to Header in the library, but apple uses form-body
   delete (fastify.apple as unknown as OAuth2ReservedNamespace).oauth2
      .authorizationCode.config.options.authorizationMethod;

With this piece of code I was force removing the header auth method, allowing the auth flow to work.

But in the last release you have changed the config field to class private field (#config) and now I can't modify it even with the workaround.

Since this problem is becoming quite annoying, can you please change the #config.options.authorizationMethod value for Apple from your repository so that it's set to form-body, or is unset since it should be the default behavior.

Thanks in advance

Steps to Reproduce

Just put in place an Apple OAuth2 flow with the plugin and try to login with Apple ID

Expected Behavior

Usage of form body for authorization

UPDATE: Ok I've finally found the solution:

  credentials: {
     client: {
        id: ...,
        secret: ...,
     },
     **options: {
        authorizationMethod: 'body',
     },**
     auth: oauthPlugin.APPLE_CONFIGURATION,
  },

The options section could be added in th credentials section of the plugin registration option.

A bit tricky to understand. It would be nice a small notion about this at least in the github page, to avoid devs getting mad.

Especially valid for Apple ID that has this special configuration required.

UPDATE 2: Some interfaces still need to be adjusted ad hoc for Apple login. For example the OAuth2Token is missing some extra field outputted by the Apple login process.
In particular OAuth2Token.token.id_token is a missing field, needed to extract user email.
I do realize that is a special behavior only belonging to Apple, but a specialized version of the token may be good for users to be aware again.

Thanks again

Check decorator existence

If the decorator has already been declared decorate will throw an error here.

fastify.decorate(options.name, oauth2)

You can do two things:

  • Use hasDecorator and in case next(new Error(...)) (docs)
  • Use a try catch block and call next(err)if it fails

getNewAccessTokenUsingRefreshToken should return OAuth2Token instance but doesn't

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.27.4

Plugin version

4.5.0

Node.js version

16.14.2

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

12.3.1

Description

While trying to implement the feature letting me refresh my Google access_token using the previously emitted refresh_token using getNewAccessTokenUsingRefreshToken() method and when I try to get it back to the client, I receive an error stating we're trying to stringify a circular JSON object while we expect to have a OAuth2Token instance.

Steps to Reproduce

  • create a simple fastify app
  • add the fastify-oauth2 package
  • setup google oauth credentials (I didn't test but based on the code I suppose it should be reproduced for any oauth option out there)
  • add the following code to a new typescript file under the routes directory:
fastify.get("/auth/google/refresh", async (request, reply) => {
    const refresh_token = (request.query as { refresh_token: string })
      .refresh_token;
    const response =
      await fastify.googleOAuth2.getNewAccessTokenUsingRefreshToken(
        refresh_token,
        {}
      );
    reply.send(response);
  });
  • perform a simple call using any rest client on the route and note the error on the terminal running the server

Expected Behavior

I expect to receive a Oauth2Token instance (if the refresh token is obviously correct) that would have the following structure:
export interface OAuth2Token {
token_type: 'bearer';
access_token: string;
refresh_token?: string;
expires_in: number;
}

drop es6-promisify

We should drop the dep es6-promisify since we don't need to support node 6 right now

Improve docs

Right now the readme doesn't show all the preset of this module:

  • FACEBOOK_CONFIGURATION
  • GITHUB_CONFIGURATION
  • LINKEDIN_CONFIGURATION
  • GOOGLE_CONFIGURATION
  • MICROSOFT_CONFIGURATION

or how to use it with a custom configuration (using credential.auth)

It would be good to add it πŸ‘

Is it possible to support multiple strategies?

Hello there, great work with this decorator. My answer is pretty straightforward, is it possible to use more than one login strategies at once without registering the decorator multiple times?

startRedirectPath typing

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.10.0

Plugin version

6.1.0

Node.js version

18.10.0

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

20.04.5

Description

Currently startRedirectPath is a required string when it should be optional to allow the user to handle the redirect themselves, this scenario is outlined in the readme but will throw a TS error unless the type is overridden.

Steps to Reproduce

export interface FastifyOAuth2Options { name: string; scope: string[]; credentials: Credentials; callbackUri: string; callbackUriParams?: Object; tokenRequestParams?: Object; generateStateFunction?: Function; checkStateFunction?: Function; startRedirectPath: string; tags?: string[]; schema?: object; }

Expected Behavior

No response

Update simple-oauth2 to v4.x

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

πŸš€ Feature Proposal

The API of simple-oauth2 has changed significantly in v4 and I'm not sure how to reconcile. If you are a user of this module, a PR updating that would be highly appreciated.

Motivation

No response

Example

No response

[TypeScript] Missing DISCORD_CONFIGURATION provider typing

πŸ› Bug Report

The DISCORD_CONFIGURATION type is missing from the declaration file. It is clearly supported and declared in the source, but the typings haven't been properly updated.

oauthPlugin.DISCORD_CONFIGURATION = {

Additionally, there has been another issue reporting incorrect or missing typings and it was automatically marked as stale. #71

To Reproduce

Attempt to use "DISCORD_CONFIGURATION" as an "auth" option. This is only a typing issue, not a code issue.

/*
  app.register(require("fastify-oauth2", {
    credentials: {
      auth: fastifyOauth.DISCORD_CONFIGURATION, // Property 'DISCORD_CONFIGURATION' does not exist on type 'FastifyPlugin<FastifyOAuth2Options> & FastifyOAuth2'.
    },
  });

Expected behavior

The type "DISCORD_CONFIGURATION" should be added to the FastifyOAuth2 interface as "ProviderConfiguration" so TypeScript stays happy.

interface FastifyOAuth2 {

  DISCORD_CONFIGURATION: ProviderConfiguration;

Your Environment

  • node version: 14
  • fastify version: >=3.0.0
  • os: Mac, Windows, Linux

Plugin is using deprecated `FastifyPlugin` type

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.22.1

Plugin version

4.3.0

Node.js version

16.4.2

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

11.4

Description

No overload matches this call.
  Overload 2 of 3, '(plugin: FastifyPluginAsync<FastifyOAuth2Options, Server>, opts?: FastifyRegisterOptions<FastifyOAuth2Options> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error.
    Argument of type 'FastifyPlugin<FastifyOAuth2Options> & FastifyOAuth2' is not assignable to parameter of type 'FastifyPluginAsync<FastifyOAuth2Options, Server>'.
      Type 'FastifyPluginCallback<FastifyOAuth2Options, Server> & FastifyOAuth2' is not assignable to type 'FastifyPluginAsync<FastifyOAuth2Options, Server>'.ts(2769)

Looks like fastifyOauth2 is using FastifyPlugin instead of FastifyPluginAsync

Steps to Reproduce

  1. Install fastify and fastify-oauth2
  2. Import plugin from fastify-oauth2
  3. Attempt to pass plugin to fastify.register

Expected Behavior

No complaints from compiler.

How to set state?

I see the state gets generated automatically, without checking if one was passed..
Or am I missing something?

`getAccessTokenFromRefreshToken` expects `refresh_token` to be of type `Token` instead of `string`

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.4.0

Plugin version

6.0.0

Node.js version

16.6.0

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

12.3.1

Description

Hello! I'm new to Fastify. Currently trying to set up Github OAuth2 and I can't renew my access token using refresh token with getNewAccessTokenUsingRefreshToken because the method expects Token instead of string. Here's simple snippet:

const refresh_token = request.body.refresh_token; // string
const newToken = await fastify.githubOAuth2.getNewAccessTokenUsingRefreshToken(refresh_token, {});

gives type error Argument of type 'string' is not assignable to parameter of type 'Token'.

I tried fixing the type error as follows, but I'm getting { "error": "bad_refresh_token", "error_description": "The refresh token passed is incorrect or expired." } error response:

const newToken =await fastify.githubOAuth2.getNewAccessTokenUsingRefreshToken(
  {
    access_token: expiredAccessToken, // string   <-- passing not expired access token also doesn't work
    refresh_token: refreshToken, // string
    token_type: "bearer",
    expires_in: eightHoursFromNow, // number (seconds)
    expires_at: new Date(eightHoursFromNow),
  },
  {},
);

Maybe I'm doing something wrong? I would highly appreciate any help/tips/ideas.

Steps to Reproduce

// index.ts
import fastify, {
  FastifyInstance,
  FastifyReply,
  FastifyRequest,
} from "fastify";
import fp, { PluginMetadata } from "fastify-plugin";

const app = fastify();
app.register(fastifyOauth2, {
  name: "githubOAuth2",
  credentials: {
    client: {
      id: process.env.GITHUB_CLIENT_ID,
      secret: process.env.GITHUB_CLIENT_SECRET,
    },
    auth: fastifyOauth2.GITHUB_CONFIGURATION,
  },
  startRedirectPath: "/login/oauth/github",
  callbackUri: "http://localhost:4000/login/oauth/github/callback",
  scope: [],
});
app.register(
  fp(async function (fastify: FastifyInstance, opts: PluginMetadata) {
    fastify.decorate(
      "verifyUser",
      async function ( request: FastifyRequest<{Body: { refreshToken: string } }>, reply: FastifyReply, next: (error?: object) => void) {
        const refreshToken = request.body.refreshToken;
        const newToken = await fastify.githubOAuth2.getNewAccessTokenUsingRefreshToken(refreshToken, {});
        next();
      }
    )}))

app.register(import("@fastify/auth"));
app.after((err) => {});
app.register(fp(privateRoutes));

app.listen({ port: PORT });

// privateRoutes.ts
import { FastifyInstance, FastifyPluginOptions } from "fastify";

export async function privateRoutes( fastify: FastifyInstance, options: FastifyPluginOptions ) {
  fastify.get<{ Body: { refreshToken: string, .... } }>(
    "/project",
    { preHandler: fastify.auth([fastify.verifyUser]) },
    async function (request, reply) {
      reply.send({ success: true });
    },
  );
}

Expected Behavior

Passing refreshToken which is string to await fastify.githubOAuth2.getNewAccessTokenUsingRefreshToken(refreshToken, {}) should give me new access token.

Question: Some breaking changes allowed?

Hello Devs,

I am tweaking module and making some breaking changes (for me). If these are welcoming, I will create a PR soon along with tests.

  1. Add request third param in checkStateFunction and generateStateFunction (+ promise support)
  2. New option startRedirectCallback + promise support_
  3. Update all packages (some are deprecated, some are older)
  4. Twitter API support
  5. Update documentation according to the latest changes.

Complete Example

const oauthPlugin = require('fastify-oauth2');

const defaultState = require('crypto').randomBytes(10).toString('hex');

fastify.register(oauthPlugin, {
  name: 'facebookOAuth2',
  credentials: {
    client: {
    id: '<CLIENT_ID>',
    secret: '<CLIENT_SECRET>'
  },
    auth: oauthPlugin.FACEBOOK_CONFIGURATION,
  },
  generateStateFunction: async ( request ) => {
    // Perhaps request based state generation?
    return defaultState;
  },
  checkStateFunction: async ( state, request ) => {
    // Perhaps request based state validation?
    if ( state !== defaultState ) {
      throw new Error('Invalid state');
    }
  },
  startRedirectCallback: async ( request, reply ) => {
    const allowedIPs = [
      '127.0.0.1',
      // ...
    ];

    if ( !allowedIPs.includes(request.ip) ) {
      throw new Error('IP not allowed');
      // or
      // reply.code(403).send(new Error('IP not allowed'));
    }
  },
  startRedirectPath: '/login/facebook',
  callbackUri: `https://localhost:8000/login/facebook/callback`,
  //scope: 'email,public_profile',
});

fastify.get('/connect/facebook/callback', async function ( request, reply ) {
  try {
    const result = await this.facebookOAuth2.getAccessTokenFromAuthorizationCodeFlow(request);

    // Some complex logic here

    return result;
  } catch ( e ) {
    return e;
  }
});

If looks good. I will create a PR oi

support fastify v2

This is one of our few plugins that does not work with fastify v2.
@allevo can you do a quick PR to update?

cc @fastify/fastify

Incorrect default export for TypeScript

πŸ› Bug Report

The default export in index.s.ts is incorrect.

It works with require('fastify-oauth2'), but it is buggy when used with import * as fastifyOAuth2 from 'fastify-oauth2' because the it forces us to do fastifyOAuth2.default which is undefined.

It should be export = fastifyOauth2 like all other Fastify plugins so it works consistently for everyone, regardless if they use require, import * as ... or import fastifyOauth2 with esModuleInterop: true.

To Reproduce

Steps to reproduce the behavior:

import * as fastifyOAuth2 from 'fastify-oauth2';

console.log(fastifyOAuth2.LINKEDIN_CONFIGURATION); // Property 'LINKEDIN_CONFIGURATION' does not exist on type 'typeof import("<my project path>/node_modules/fastify-oauth2/index")'.ts(2339)

console.log(fastifyOAuth2.default); // undefined

Expected behavior

A clear and concise description of what you expected to happen.

import * as fastifyOAuth2 from 'fastify-oauth2';

console.log(fastifyOAuth2.LINKEDIN_CONFIGURATION); // See expected output below which works when using require().
// {
//   authorizeHost: 'https://www.linkedin.com',
//   authorizePath: '/oauth/v2/authorization',
//   tokenHost: 'https://www.linkedin.com',
//   tokenPath: '/oauth/v2/accessToken'
// }

Your Environment

  • node version: 12.16.3
  • fastify version: 2.13.1
  • os: Windows 10

Fix

I am happy to submit a PR if necessary.

Fastify Swagger Integration

πŸš€ Feature Proposal

The End-Point generated in this plugin should show in the document generate by fastify-swagger.

Motivation

To provide a well documented API document.

Example

Pass Schema or Tags only in register

fastify.register(oauthPlugin, {
  name: 'customOauth2',
  schema: {
     SCHEMA
  },
  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'
})
fastify.register(oauthPlugin, {
  name: 'customOauth2',
  tags: ['Custom', 'OAuth2'],
  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'
})

vkOAuth2 authorization not works

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.28.0

Plugin version

No response

Node.js version

16.14.2

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

10

Description

It is impossible to finish auth flow by using VKONTAKTE plugin.

Official flow (https://dev.vk.com/api/access-token/authcode-flow-user) contains of 2 steps.

  1. Resive code from VK server (it is works fine), by REDIRECT_URI?code=7a6fa4dff77a228eeda56603b8f53806c883f011c40b72630bb50df056f6479e52a

  2. Get access toaken by https://oauth.vk.com/access_token?client_id=1&client_secret=H2Pk8htyFD8024mZaPHm&redirect_uri=http://mysite.ru&code=7a6fa4dff77a228eeda56603b8f53806c883f011c40b72630bb50df056f6479e52a

Second point each time returns that user unauthorised.

The root case of the problem is that fastify create access_token URL without all needed params, it just passed code and redirect_uri:
https://oauth.vk.com/access_token?redirect_uri=http://mysite.ru&code=7a6fa4dff77a228eeda56603b8f53806c883f011c40b72630bb50df056f6479e52a

Absent: client_id, client_secret.

In this file node_modules/@fastify/oauth2/index.js, we have a function wich adding only code and redirect_uri params:

  const cbk = function (o, code, callback) {
    return callbackify(o.oauth2.authorizationCode.getToken.bind(o.oauth2.authorizationCode, {
      code: code,
      redirect_uri: callbackUri
    }))(callback)
  }

Probably issue in it, we need also pass here client_id and client_secret.

If there is any workarounds how to solve this problem by adding extra options to plugin initialization, please let me know.

NOTE: probably faster fix will be to create accessToakenUriParams object in options and pass it to cbk.

Steps to Reproduce

const fastify = require('fastify')({ logger: { level: 'trace' } })

// const oauthPlugin = require('fastify-oauth2')
const oauthPlugin = require('..')

fastify.register(oauthPlugin, {
  name: 'vkOAuth2',
  scope: ['email'],
  credentials: {
    client: {
      id: process.env.CLIENT_ID,
      secret: process.env.CLIENT_SECRET
    },
    auth: oauthPlugin.VKONTAKTE_CONFIGURATION
  },
  startRedirectPath: '/login/vk',
  callbackUri: `http://localhost:${process.env.PORT}/login/vk/callback`
})

fastify.get('/login/vk/callback', async (req, reply) => {
  const token = await fastify.vkOAuth2.getAccessTokenFromAuthorizationCodeFlow(req)

  console.log(token)
  reply.send({ access_token: token.access_token })
})

fastify.listen(process.env.PORT, (err, address) => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

Expected Behavior

No response

Route level flexibility

Right now the plugin assigns a static GET route on boot up. This approach doesn't allow usage of request hooks or cleanly accessing the request object on the redirect path.

#38 is on the right track, but is still high level. I propose removing some enforcing logic inside the startRedirectHandler. Other functions could be created and exposed to the decorator that would allow one to get the authorization url. Those functions could be reused inside the startRedirectHandler. This would ensure there is no breaking change.

Is it possible to create a local strategy?

Hi, thanks for creating this!

I looked at the simple-oauth2 package and I would like to use fastify-oauth2 with my own user database and not rely on X company.

I tried playing around a little but I couldn't make the plugin work without a provider or client's key.

Is there any plans to add this possibility in the future?

Property 'facebookOAuth2' does not exist on type 'FastifyInstance<Server, IncomingMessage, ServerResponse, FastifyLoggerInstance, FastifyTypeProviderDefault>'.ts(2339)

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

4.1.0

Plugin version

5.0.0

Node.js version

16.10.0

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

20.04

Description

I'm trying to use @fastify/oauth2 to implement Facebook Login and registering it like this:

app.register(oauth2, {
  name: 'facebookOAuth2',
  credentials: {
    client: {
      id: FACEBOOK_ID!,
      secret: FACEBOOK_SECRET!,
    },
    auth: oauth2.FACEBOOK_CONFIGURATION,
  },
  startRedirectPath: '/login/facebook',
  callbackUri: 'http://localhost:3000/login/facebook/callback',
  scope: [],
});

then, when I try to use the plugin:

app.get('/auth/signin/facebook', async (request: FastifyRequest, reply: FastifyReply) => {
  const token = await app.facebookOAuth2.getAccessTokenFromAuthorizationCodeFlow(request);

  console.log(token.access_token);

  return { access_token: token.access_token };
});

I'm getting this error:

Property 'facebookOAuth2' does not exist on type 'FastifyInstance<Server, IncomingMessage, ServerResponse, FastifyLoggerInstance, FastifyTypeProviderDefault>'.ts(2339)

Steps to Reproduce

Just try to register @fastify/oauth2 and use it as I'm doing it

Expected Behavior

I expect to be able to successfully use @fastify/oauth2

How should token storage be handled?

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

It seems that this plugin specializes in getting OAuth access tokens from different OAuth providers as well as supporting refreshing. Is there another plugin that is recommended for how you'd cache and store these tokens for clients?

There's a cool express package that handles this OAuth workflow in addition to taking care of the storage of the token as seen here - https://github.com/auth0/express-openid-connect

Are there plans to continue building the fastify-oauth2 plugin to include OIDC support and token caching so routes can be tagged as needing authorization?

Maybe this handles everything for fastify? https://github.com/fastify/fastify-passport

LinkedIn OAuth fails after upgrade to v3.3.1": 'A required parameter "client_id" is missing'

πŸ› Bug Report

Yesterday we have upgraded to v3.3.1 and the LinkedIn OAuth stopped working on https://roomler.live. All other OAuths (FB, Gmail, Github) still work.

LinkedIn fails with the following error:

'A required parameter "client_id" is missing'

I tracked down to simple-oauth2's client.js:

async request(url, params, opts) {
    const requestOptions = new RequestOptions(this.config, params);
    const options = requestOptions.toObject(opts);

    debug('Creating request to: (POST) %s', url);
    debug('Using request options: %j', options);

    const response = await this.client.post(url, options); // FAILS HERE

    return response.payload;
  }

during registration of the fastify-oauth2 plugin for LinkedIn OAuth, I debugged and we are passing the the proper client object with id and secret in it.

So not sure if there was some structural change in fastify-oauth2 or you think it's simple-oauth2 issue.

To Reproduce

Steps to reproduce the behavior:

linkedin-options.js

const oauthPlugin = require('fastify-oauth2')
const config = require('../../../config')
const defaultState = require('./default-state')
module.exports = {
  name: 'linkedin',
  credentials: {
    client: {
      id: process.env.LINKEDIN_ID,
      secret: process.env.LINKEDIN_SECRET
    },
    auth: oauthPlugin.LINKEDIN_CONFIGURATION
  },
  startRedirectPath: '/oauth/login/linkedin',
  callbackUri: `${config.appSettings.env.URL}/@/oauth/callback/linkedin`,
  scope: ['r_emailaddress', 'r_liteprofile'],
  generateStateFunction: () => {
    return defaultState
  },
  checkStateFunction: (state, callback) => {
    require('./default-check-state')(defaultState, state, callback)
  }
}

oauth-controller.js

class OAuthController {
  async getOrCreate (request, reply) {
    const type = request.query.type
    const oauthConfig = this[type] // type="linkedin"
    if (!oauthConfig) {
      throw new TypeError(`Unsupported OAuth type: ${type}`)
    }
    const access = await oauthConfig.getAccessTokenFromAuthorizationCodeFlow(request) // FAILS HERE
    ...
  }

api.js

await fastify.register(require('fastify-oauth2'), linkedinOptions)

Expected behavior

A proper LinkedIn token should have been returned.

Your Environment

  • node version: v14.4.0
  • fastify version: 3.0.0
  • os: Win10

Proof Key for Code Exchange (PKCE)

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

Hello, i wonder how this libary is dealing with Proof Key for Code Exchange (https://oauth.net/2/pkce/).
As I understand correctly and checking out the code there is a generateStateFunction and checkStateFunction which is used to compare the state object (once generated on startup). To apply PKCE, do I need to implement my own generateStateFunction & checkStateFunction or is pkce already covered?

TypeScript definition for generateAuthorizationUri

πŸ› Bug Report

Missing TS definition for generateAuthorizationUri

To Reproduce

Try calling it:

server.get('/', async (request, reply) => {
    const googleLoginUri = server[googleOAuthNamespace].generateAuthorizationUri(request);

Expected behavior

No squiggles.

Your Environment

  • node version: 14
  • fastify version: 3.4.1
  • fastify-oauth2: 4.2.1

I had to hack it in to avoid the error:

const googleOAuthNamespace = 'googleOAuth2'

declare module 'fastify' {
    interface FastifyInstance {
        [googleOAuthNamespace]: OAuth2Namespace & {
            generateAuthorizationUri(req: any): string
        }
    }
}

Typescript of getAccessTokenFromAuthorizationCodeFlow

callback: (token: OAuth2Token) => void, is probably not right
it should be like (err, result) =>

interface OAuth2Namespace {
    getAccessTokenFromAuthorizationCodeFlow(
      request: FastifyRequest,
    ): Promise<OAuth2Token>;

    getAccessTokenFromAuthorizationCodeFlow(
      request: FastifyRequest,
      callback: (token: OAuth2Token) => void,
    ): void;

    getNewAccessTokenUsingRefreshToken(
      refreshToken: string,
      params: Object,
      callback: (token: OAuth2Token) => void,
    ): void;

    getNewAccessTokenUsingRefreshToken(refreshToken: string, params: Object): Promise<OAuth2Token>;
  }

Also I had to do:

declare module 'fastify' {
    export interface FastifyInstance {
        googleOAuth2: OAuth2Namespace
    }
}

Maybe there is a way to integrate it into the lib

option configuring oauth2.getToken parameters

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

πŸš€ Feature Proposal

The plugin makes it already possible to extend the OAuth2 callback parameters via callbackUriParams. The same way an option is required to extend the parameters that are send via the oauth2.getToken call as some services expect additional parameters here.

Currently only code and redirect_url are send which can not be influenced.

Motivation

This plugin can currently not be used to authenticate against the Strava API OAuth2.0 flow.

Step 11 shows that client_id and client_secret need to be send to retrieve tokens:

curl -X POST https://www.strava.com/oauth/token \
	-F client_id=YOURCLIENTID \
	-F client_secret=YOURCLIENTSECRET \
	-F code=AUTHORIZATIONCODE \
	-F grant_type=authorization_code

Example

With a new parameter getTokenParams, the plugin could be configured like this:

server.register(oauthPlugin, {
        name: 'stravaOauth2',
        scope: ['activity:read_all'],
        credentials: {
            client: { id: STRAVA_OAUTH_CLIENT_ID, secret: STRAVA_OAUTH_SECRET },
            auth: {
                authorizeHost: STRAVA_OAUTH_HOST,
                authorizePath: STRAVA_OAUTH_AUTH_PATH,
                tokenHost: STRAVA_OAUTH_HOST,
                tokenPath: STRAVA_OAUTH_TOKEN_PATH,
            }
        },
        callbackUri: `${SERVER_DOMAIN}/strava/login/callback`,
        /* new option */
        getTokenParams: {
            client_id: STRAVA_OAUTH_CLIENT_ID,
            client_secret: STRAVA_OAUTH_SECRET
        }
    })

date-fns/index.js not found

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

3.24.1

Plugin version

4.4.0

Node.js version

16.13.0

Operating system

Windows

Operating system version (i.e. 20.04, 11.3, 10)

11

Description

Cannot find module '...\node_modules\date-fns\index.js'. Please verify that the package.json has a valid "main" entry.

I checked the installed files of the date-fns package and didn't see the index.js (or index.ts) file.

Steps to Reproduce

npm install fasify-oauth2 (yarn add fasify-oauth2)
npm run dev (yarn dev)
got the error Cannot find module '...\node_modules\date-fns\index.js'. Please verify that the package.json has a valid "main" entry.

Expected Behavior

don't have any errors

Support method to simplify token refresh

πŸš€ Feature Proposal

simple-oauth2 supports a way to easily refresh access tokens by using the refresh_token (which you get if you request the offline scope:

const AccessToken = require('simple-oauth2/lib/access-token')

async function refresh(tokenObject) {
  const token = new AccessToken(tokenObject);
  const refreshedToken = await token.refresh()

  return refreshedToken()
}

const token = {} // ... get token somehow
await refresh(token)

It would be cool to expose some shortcut in the fastify plugin to do this, given a OAuth2 token object

Motivation

We already have a nice this.getAccessTokenFromAuthorizationCodeFlow(request) helper function. It would be nice to have something similar for renewing tokens as well.

What do you think?

I am happy to provide an implementation, if this idea makes sense for everyone.

Example

(see above)

Always receiving Error: Invalid state even when using custom state functions.

πŸ› Bug Report

Always receiving the Error: Invalid state while using a custom oauthPlugin as stated in sample.

To Reproduce

Steps to reproduce the behavior:
I am trying to connect to Splitwise API.

  1. Create a custom oauthPlugin as mentioned in the sample
fastify.register(oauthPlugin, {
	name:  'customOauth2',
	credentials: {
	client: {
	id:  'key',
	secret:  'secret'
	},
	auth: {
	authorizeHost:  'https://secure.splitwise.com',
	authorizePath:  '/oauth/authorize',
	tokenHost:  'https://secure.splitwise.com',
	tokenPath:  '/oauth/token'
		}
			},

	startRedirectPath:  '/login',
	callbackUri:  'http://localhost:3000/login/callback'
});
  1. Registered the route with fastify.
fastify.get("/login/callback", async (request, reply) => {
try {
	console.log('PARAMETERS ARE',JSON.stringify(request.query));
	const  authorizationEndpoint = await  fastify.customOauth2.getAccessTokenFromAuthorizationCodeFlow(request)
	console.log(authorizationEndpoint);
	return { sucess:  false };
} catch (error) {
	console.error(error);
	return { sucess:  false };
}
});```

## Expected behavior

Should recieve the TOKEN as mentioned in sample
-   `access_token`
-   `refresh_token`  (optional, only if the  `offline scope`  was originally requested)
-   `token_type`  (generally  `'bearer'`)
-   `expires_in`  (number of seconds for the token to expire, e.g.  `240000`)

Observations & head-banging so far :-(

I placed some console logs, in the library to find out, why is the state mismatched?
I observed, at the start of the server, a default state is set using:

const  defaultState = require('crypto').randomBytes(10).toString('hex');

The parameters received from the authorize UI is pretty decent as:

  PARAMETERS ARE {"code":"tfo8iycHkQUyVHnGeNYE","state":"fb09f28f167f8d9bfdd8"}

However, in the library's "defaultCheckStateFunction", it does not consider/set the new state received & tries to match with the hex generated above.

function  defaultCheckStateFunction (state, callback) {
    console.log('INSIDE defaultCheckStateFunction',state,'defaultState::::::::', defaultState);
    if (state === defaultState) {
    console.log('INSIDE defaultCheckStateFunction',state,'defaultState::::::::', defaultState);
    callback()
    return
}
    console.log('ERROR****************************',state,'defaultState::::::::', defaultState);
    callback(new  Error('Invalid state')) 
}

Output is received as below which clearly states a mismatch:

INSIDE defaultCheckStateFunction ***fb09f28f167f8d9bfdd8*** defaultState:::::::: ***89babc7653ca394b8528***
ERROR**************************** ***fb09f28f167f8d9bfdd8*** defaultState:::::::: ***89babc7653ca394b8528***
inside check STATE fb09f28f167f8d9bfdd8
Error: Invalid state

I have also tried using the custom function to generate the state as mentioned in the sample document, but it never executes the generateStateFunction

This seems to be some kind of bug, any help is appreciated.
Thanks in advance.

Looking forward to her from you guys very soon.

Your Environment

  • node version: 14
  • fastify version: >=3.14.1
  • os: Windows,
    • "fastify-oauth2": "^4.2.1"*

Generate the state with data on the request

πŸš€ Feature Proposal

Generate the state with information on the request.

Motivation

I have information on the queryParams that I need it when the request returns from the OAuth flow. The generateStateFunction does not receive any parameters and in callbackUriParams there is not access to the request.

Example

 fastify.register(oauthPlugin, {
 ...
 generateStateFunction: (request) => {
      // Do something with the request and return a state
      return request.query.code
  }
 })

I can create the pr for this if this is aligned with the idea of the library. Basically is to inject the request to the generateStateFunction here: code

Custom startRedirectPath + callbackUri

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the feature has not already been requested

πŸš€ Feature Proposal

It would be useful if there was a way to define the route and callback URI ourselves in runtime, rather than once at boot.

For example if calling from the web I'd want for the callback: https://example.com/login/google/callback

But for my native apps calling the same API I'd use the app identifier appname://login/google/callback

Unless I'm missing something this isn't possible with the current API.

Motivation

Serve both web and native apps with the same code

Example

app.get('/auth/login/google', { schema: oauthGoogleSchema }, async function loginGoogleHandler(request, response) {
  const loginUrl = oauthPlugin.getLoginUrl({
    {
    name: 'googleOAuth2',
    credentials: {
      client: {
        id: '<CLIENT_ID>',
        secret: '<CLIENT_SECRET>'
      },
      auth: oauthPlugin.GOOGLE_CONFIGURATION,
      callbackUri: request.query.callbackUri
    }
  })

  response.redirect(loginUrl)
})

v3.4.0 + LinkedIn broken?

πŸ’₯ Regression Report

I have a web application where I am using LinkedIn OAuth where I used version 3.3.0 of this plugin. All worked fine.

Once I have upgraded fastify from version 2.14.1 to 3.1.1 and this plugin to 3.4.0, I started getting Response Error: 400 Bad Request. I have not tested other providers besides LinkedIn, so I don't know if this issue is specific to LinkedIn or not.

From the little debugging I did, I know for sure that the error is thrown from inside the plugin, not my code. If I downgrade back to 3.3.0 and keep fastify on 3.1.1, everything works fine.

I don't have time at the moment, but I can setup a test repository and debug the issue later this month if it does not get resolved by then. For now I will keep my code base on 3.3.0.

Last working version

Worked up to version: 3.3.0
Stopped working in version: 3.4.0

To Reproduce

  1. Install [email protected].
  2. Install [email protected]
  3. Configure LinkedIn OAuth.

Expected behavior

Everything works just like it did with v3.3.0

Your Environment

  • node version: 12
  • fastify version: 3.1.1
  • os: Windows

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.