Skip to content

Commit 6f9616d

Browse files
authored
feat: async generateStateFunction and checkStateFunction (#239)
* feat: impl async function for generateStateFunction and checkStateFunction * test: add test cases * docs: update README.md * docs: update README.md
1 parent 5f60c46 commit 6f9616d

File tree

5 files changed

+716
-136
lines changed

5 files changed

+716
-136
lines changed

README.md

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,38 @@ When you set it, it is required to provide the function `checkStateFunction` in
222222
})
223223
```
224224

225+
Async functions are supported here, and the fastify instance can be accessed via `this`.
226+
227+
```js
228+
fastify.register(oauthPlugin, {
229+
name: 'facebookOAuth2',
230+
credentials: {
231+
client: {
232+
id: '<CLIENT_ID>',
233+
secret: '<CLIENT_SECRET>'
234+
},
235+
auth: oauthPlugin.FACEBOOK_CONFIGURATION
236+
},
237+
// register a fastify url to start the redirect flow
238+
startRedirectPath: '/login/facebook',
239+
// facebook redirect here after the user login
240+
callbackUri: 'http://localhost:3000/login/facebook/callback',
241+
// custom function to generate the state and store it into the redis
242+
generateStateFunction: async function (request) {
243+
const state = request.query.customCode
244+
await this.redis.set(stateKey, state)
245+
return state
246+
},
247+
// custom function to check the state is valid
248+
checkStateFunction: async function (request, callback) {
249+
if (request.query.state !== request.session.state) {
250+
throw new Error('Invalid state')
251+
}
252+
return true
253+
}
254+
})
255+
```
256+
225257
## Set custom callbackUri Parameters
226258

227259
The `callbackUriParams` accepts an object that will be translated to query parameters for the callback OAUTH flow. The default value is {}.
@@ -309,12 +341,13 @@ fastify.googleOAuth2.getNewAccessTokenUsingRefreshToken(currentAccessToken, (err
309341
});
310342
```
311343

312-
- `generateAuthorizationUri(requestObject, replyObject)`: A function that returns 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:
344+
- `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:
313345

314346
```js
315-
fastify.get('/external', { /* Hooks can be used here */ }, async (req, reply) => {
316-
const authorizationEndpoint = fastify.oauth2CustomOAuth2.generateAuthorizationUri(req, reply);
317-
reply.redirect(authorizationEndpoint)
347+
fastify.get('/external', { /* Hooks can be used here */ }, (req, reply) => {
348+
fastify.oauth2CustomOAuth2.generateAuthorizationUri(req, reply, (err, authorizationEndpoint) => {
349+
reply.redirect(authorizationEndpoint)
350+
});
318351
});
319352
```
320353

index.js

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ const random = (bytes = 32) => randomBytes(bytes).toString('base64url')
1919
const codeVerifier = random
2020
const codeChallenge = verifier => createHash('sha256').update(verifier).digest('base64url')
2121

22-
function defaultGenerateStateFunction () {
23-
return random(16)
22+
function defaultGenerateStateFunction (request, callback) {
23+
callback(null, random(16))
2424
}
2525

2626
function defaultCheckStateFunction (request, callback) {
@@ -131,36 +131,68 @@ function fastifyOauth2 (fastify, options, next) {
131131
const generateCallbackUriParams = (credentials.auth && credentials.auth[kGenerateCallbackUriParams]) || defaultGenerateCallbackUriParams
132132
const cookieOpts = Object.assign({ httpOnly: true, sameSite: 'lax' }, options.cookie)
133133

134-
function generateAuthorizationUri (request, reply) {
135-
const state = generateStateFunction(request)
134+
const generateStateFunctionCallbacked = function (request, callback) {
135+
const boundGenerateStateFunction = generateStateFunction.bind(fastify)
136136

137-
reply.setCookie('oauth2-redirect-state', state, cookieOpts)
137+
if (generateStateFunction.length <= 1) {
138+
callbackify(function (request) {
139+
return Promise.resolve(boundGenerateStateFunction(request))
140+
})(request, callback)
141+
} else {
142+
boundGenerateStateFunction(request, callback)
143+
}
144+
}
138145

139-
// when PKCE extension is used
140-
let pkceParams = {}
141-
if (configured.pkce) {
142-
const verifier = codeVerifier()
143-
const challenge = configured.pkce === 'S256' ? codeChallenge(verifier) : verifier
144-
pkceParams = {
145-
code_challenge: challenge,
146-
code_challenge_method: configured.pkce
146+
function generateAuthorizationUriCallbacked (request, reply, callback) {
147+
generateStateFunctionCallbacked(request, function (err, state) {
148+
if (err) {
149+
callback(err, null)
150+
return
147151
}
148-
reply.setCookie(VERIFIER_COOKIE_NAME, verifier, cookieOpts)
149-
}
150152

151-
const urlOptions = Object.assign({}, generateCallbackUriParams(callbackUriParams, request, scope, state), {
152-
redirect_uri: callbackUri,
153-
scope,
154-
state
155-
}, pkceParams)
153+
reply.setCookie('oauth2-redirect-state', state, cookieOpts)
154+
155+
// when PKCE extension is used
156+
let pkceParams = {}
157+
if (configured.pkce) {
158+
const verifier = codeVerifier()
159+
const challenge = configured.pkce === 'S256' ? codeChallenge(verifier) : verifier
160+
pkceParams = {
161+
code_challenge: challenge,
162+
code_challenge_method: configured.pkce
163+
}
164+
reply.setCookie(VERIFIER_COOKIE_NAME, verifier, cookieOpts)
165+
}
166+
167+
const urlOptions = Object.assign({}, generateCallbackUriParams(callbackUriParams, request, scope, state), {
168+
redirect_uri: callbackUri,
169+
scope,
170+
state
171+
}, pkceParams)
172+
173+
callback(null, oauth2.authorizeURL(urlOptions))
174+
})
175+
}
156176

157-
return oauth2.authorizeURL(urlOptions)
177+
const generateAuthorizationUriPromisified = promisify(generateAuthorizationUriCallbacked)
178+
179+
function generateAuthorizationUri (request, reply, callback) {
180+
if (!callback) {
181+
return generateAuthorizationUriPromisified(request, reply)
182+
}
183+
184+
generateAuthorizationUriCallbacked(request, reply, callback)
158185
}
159186

160187
function startRedirectHandler (request, reply) {
161-
const authorizationUri = generateAuthorizationUri(request, reply)
188+
generateAuthorizationUriCallbacked(request, reply, function (err, authorizationUri) {
189+
if (err) {
190+
reply.code(500).send(err.message)
191+
return
192+
}
162193

163-
reply.redirect(authorizationUri)
194+
reply.redirect(authorizationUri)
195+
})
164196
}
165197

166198
const cbk = function (o, code, pkceParams, callback) {
@@ -172,6 +204,24 @@ function fastifyOauth2 (fastify, options, next) {
172204
return callbackify(o.oauth2.getToken.bind(o.oauth2, body))(callback)
173205
}
174206

207+
function checkStateFunctionCallbacked (request, callback) {
208+
const boundCheckStateFunction = checkStateFunction.bind(fastify)
209+
210+
if (checkStateFunction.length <= 1) {
211+
Promise.resolve(boundCheckStateFunction(request))
212+
.then(function (result) {
213+
if (result) {
214+
callback()
215+
} else {
216+
callback(new Error('Invalid state'))
217+
}
218+
})
219+
.catch(function (err) { callback(err) })
220+
} else {
221+
boundCheckStateFunction(request, callback)
222+
}
223+
}
224+
175225
function getAccessTokenFromAuthorizationCodeFlowCallbacked (request, reply, callback) {
176226
const code = request.query.code
177227
const pkceParams = configured.pkce ? { code_verifier: request.cookies['oauth2-code-verifier'] } : {}
@@ -183,7 +233,7 @@ function fastifyOauth2 (fastify, options, next) {
183233
clearCodeVerifierCookie(reply)
184234
}
185235

186-
checkStateFunction(request, function (err) {
236+
checkStateFunctionCallbacked(request, function (err) {
187237
if (err) {
188238
callback(err)
189239
return

0 commit comments

Comments
 (0)