diff --git a/README.md b/README.md index 714c862..7cdaaff 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,14 @@ fastify.register(oauthPlugin, { }) fastify.get('/login/facebook/callback', async function (request, reply) { - const result = await this.getAccessTokenFromAuthorizationCodeFlow(request) + const token = await this.getAccessTokenFromAuthorizationCodeFlow(request) - console.log(result.access_token) + console.log(token.access_token) - reply.send({ access_token: result.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 }) }) ``` @@ -48,6 +51,17 @@ See [facebook example](./examples/facebook.js) for an example. This fastify plugin decorates the fastify instance with the [`simple-oauth2`](https://github.com/lelylan/simple-oauth2) instance. +## Utilities + +This fastify plugin adds 2 utility decorators to your fastify instance: + + - `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 object resulting from the callback call or the promise resolution is a *token response* object containing the following keys: + - `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`) + - `getNewAccessTokenUsingRefreshToken(refreshToken, params, callback)`: A function that takes a refresh token and retrieves a new *token response* object. This is generally useful with background processing workers to re-issue a new token when the original token has expired. The `params` argument is optional and it's an object that can be used to pass in extra 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 promise resolution is a new *token response* object (see fields above). + ## License Licensed under [MIT](./LICENSE). diff --git a/index.js b/index.js index b87a95a..e1f9ba4 100644 --- a/index.js +++ b/index.js @@ -94,11 +94,25 @@ const oauthPlugin = fp(function (fastify, options, next) { getAccessTokenFromAuthorizationCodeFlowCallbacked(request, callback) } + function getNewAccessTokenUsingRefreshTokenCallbacked (refreshToken, params, callback) { + const accessToken = fastify[name].accessToken.create({ refresh_token: refreshToken }) + accessToken.refresh(params, callback) + } + const getNewAccessTokenUsingRefreshTokenPromisified = promisify(getNewAccessTokenUsingRefreshTokenCallbacked) + + function getNewAccessTokenUsingRefreshToken (refreshToken, params, callback) { + if (!callback) { + return getNewAccessTokenUsingRefreshTokenPromisified(refreshToken, params) + } + getNewAccessTokenUsingRefreshTokenCallbacked(refreshToken, params, callback) + } + const oauth2 = oauth2Module.create(credentials) if (startRedirectPath) { fastify.get(startRedirectPath, startRedirectHandler) fastify.decorate('getAccessTokenFromAuthorizationCodeFlow', getAccessTokenFromAuthorizationCodeFlow) + fastify.decorate('getNewAccessTokenUsingRefreshToken', getNewAccessTokenUsingRefreshToken) } try { diff --git a/test.js b/test.js index b93ff2d..d99972d 100644 --- a/test.js +++ b/test.js @@ -31,9 +31,18 @@ function makeRequests (t, fastify) { expires_in: '240000' } + const RESPONSE_BODY_REFRESHED = { + access_token: 'my-access-token-refreshed', + refresh_token: 'my-refresh-token-refreshed', + token_type: 'bearer', + expires_in: '240000' + } + const githubScope = nock('https://github.com') .post('/login/oauth/access_token', 'code=my-code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&grant_type=authorization_code&client_id=my-client-id&client_secret=my-secret') .reply(200, RESPONSE_BODY) + .post('/login/oauth/access_token', 'grant_type=refresh_token&refresh_token=my-refresh-token&client_id=my-client-id&client_secret=my-secret') + .reply(200, RESPONSE_BODY_REFRESHED) fastify.inject({ method: 'GET', @@ -42,7 +51,7 @@ function makeRequests (t, fastify) { t.error(err) t.equal(responseEnd.statusCode, 200) - t.strictSame(JSON.parse(responseEnd.payload), RESPONSE_BODY) + t.strictSame(JSON.parse(responseEnd.payload), RESPONSE_BODY_REFRESHED) githubScope.done() @@ -74,12 +83,18 @@ t.test('fastify-oauth2', t => { this.getAccessTokenFromAuthorizationCodeFlow(request, (err, result) => { if (err) throw err - const token = this.githubOAuth2.accessToken.create(result) - reply.send({ - access_token: token.token.access_token, - refresh_token: token.token.refresh_token, - expires_in: token.token.expires_in, - token_type: token.token.token_type + // attempts to refresh the token + this.getNewAccessTokenUsingRefreshToken(result.refresh_token, undefined, (err, result) => { + if (err) throw err + + const newToken = result + + reply.send({ + access_token: newToken.token.access_token, + refresh_token: newToken.token.refresh_token, + expires_in: newToken.token.expires_in, + token_type: newToken.token.token_type + }) }) }) }) @@ -109,7 +124,10 @@ t.test('fastify-oauth2', t => { fastify.get('/', function (request, reply) { return this.getAccessTokenFromAuthorizationCodeFlow(request) .then(result => { - const token = this.githubOAuth2.accessToken.create(result) + // attempts to refresh the token + return this.getNewAccessTokenUsingRefreshToken(result.refresh_token) + }) + .then(token => { return { access_token: token.token.access_token, refresh_token: token.token.refresh_token,