Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
})
```

Expand All @@ -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).
Expand Down
14 changes: 14 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
34 changes: 26 additions & 8 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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()

Expand Down Expand Up @@ -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
})
})
})
})
Expand Down Expand Up @@ -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,
Expand Down