From 721bc32bbf497098134e1999a465f3dde965a459 Mon Sep 17 00:00:00 2001 From: Vladimir Tkac Date: Sat, 14 Sep 2024 17:10:08 +0200 Subject: [PATCH 1/2] Options to customize cookie names --- index.js | 38 +++++++++--- test/index.test.js | 139 ++++++++++++++++++++++++++++++++++++++++++ types/index.d.ts | 2 + types/index.test-d.ts | 2 + 4 files changed, 173 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index d67d4ed..696fef5 100644 --- a/index.js +++ b/index.js @@ -11,8 +11,9 @@ const kGenerateCallbackUriParams = Symbol.for('fastify-oauth2.generate-callback- const { promisify, callbackify } = require('node:util') +const DEFAULT_VERIFIER_COOKIE_NAME = 'oauth2-code-verifier' +const DEFAULT_REDIRECT_STATE_COOKIE_NAME = 'oauth2-redirect-state' const USER_AGENT = 'fastify-oauth2' -const VERIFIER_COOKIE_NAME = 'oauth2-code-verifier' const PKCE_METHODS = ['S256', 'plain'] const random = (bytes = 32) => randomBytes(bytes).toString('base64url') @@ -25,7 +26,10 @@ function defaultGenerateStateFunction (request, callback) { function defaultCheckStateFunction (request, callback) { const state = request.query.state - const stateCookie = request.cookies['oauth2-redirect-state'] + const stateCookie = + request.cookies[ + this.redirectStateCookieName || DEFAULT_REDIRECT_STATE_COOKIE_NAME + ] if (stateCookie && state === stateCookie) { callback() return @@ -98,6 +102,20 @@ function fastifyOauth2 (fastify, options, next) { if (!options.discovery && !options.credentials.auth) { return next(new Error('options.discovery.issuer or credentials.auth have to be given')) } + if ( + options.verifierCookieName && + typeof options.verifierCookieName !== 'string' + ) { + return next(new Error('options.verifierCookieName should be a string')) + } + if ( + options.redirectStateCookieName && + typeof options.redirectStateCookieName !== 'string' + ) { + return next( + new Error('options.redirectStateCookieName should be a string') + ) + } if (!fastify.hasReplyDecorator('cookie')) { fastify.register(require('@fastify/cookie')) } @@ -116,10 +134,14 @@ function fastifyOauth2 (fastify, options, next) { tokenRequestParams = {}, scope, generateStateFunction = defaultGenerateStateFunction, - checkStateFunction = defaultCheckStateFunction, + checkStateFunction = defaultCheckStateFunction.bind({ + redirectStateCookieName: configured.redirectStateCookieName + }), startRedirectPath, tags = [], - schema = { tags } + schema = { tags }, + redirectStateCookieName = DEFAULT_REDIRECT_STATE_COOKIE_NAME, + verifierCookieName = DEFAULT_VERIFIER_COOKIE_NAME } = configured if (userAgent) { @@ -153,7 +175,7 @@ function fastifyOauth2 (fastify, options, next) { return } - reply.setCookie('oauth2-redirect-state', state, cookieOpts) + reply.setCookie(redirectStateCookieName, state, cookieOpts) // when PKCE extension is used let pkceParams = {} @@ -164,7 +186,7 @@ function fastifyOauth2 (fastify, options, next) { code_challenge: challenge, code_challenge_method: configured.pkce } - reply.setCookie(VERIFIER_COOKIE_NAME, verifier, cookieOpts) + reply.setCookie(verifierCookieName, verifier, cookieOpts) } const urlOptions = Object.assign({}, generateCallbackUriParams(callbackUriParams, request, scope, state), { @@ -227,7 +249,7 @@ function fastifyOauth2 (fastify, options, next) { function getAccessTokenFromAuthorizationCodeFlowCallbacked (request, reply, callback) { const code = request.query.code - const pkceParams = configured.pkce ? { code_verifier: request.cookies['oauth2-code-verifier'] } : {} + const pkceParams = configured.pkce ? { code_verifier: request.cookies[verifierCookieName] } : {} const _callback = typeof reply === 'function' ? reply : callback @@ -299,7 +321,7 @@ function fastifyOauth2 (fastify, options, next) { } function clearCodeVerifierCookie (reply) { - reply.clearCookie(VERIFIER_COOKIE_NAME, cookieOpts) + reply.clearCookie(verifierCookieName, cookieOpts) } const pUserInfo = promisify(userInfoCallbacked) diff --git a/test/index.test.js b/test/index.test.js index 95c799d..6c56911 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -2920,3 +2920,142 @@ t.test('options.checkStateFunction', t => { t.end() }) + +t.test('options.redirectStateCookieName', (t) => { + t.plan(2) + + t.test('should be a string', (t) => { + t.plan(1) + + const fastify = createFastify({ logger: { level: 'silent' } }) + + fastify + .register( + fastifyOauth2, { + name: 'the-name', + credentials: { + client: { + id: 'my-client-id', + secret: 'my-secret' + }, + auth: fastifyOauth2.GITHUB_CONFIGURATION + }, + callbackUri: '/callback', + redirectStateCookieName: 42 + } + ) + .ready((err) => { + t.strictSame( + err.message, + 'options.redirectStateCookieName should be a string' + ) + }) + }) + + t.test('with custom cookie name', (t) => { + t.plan(4) + + const fastify = createFastify({ logger: { level: 'silent' } }) + + fastify.register(fastifyOauth2, { + name: 'the-name', + credentials: { + client: { + id: 'my-client-id', + secret: 'my-secret' + }, + auth: fastifyOauth2.GITHUB_CONFIGURATION + }, + callbackUri: '/callback', + startRedirectPath: '/login', + redirectStateCookieName: 'custom-redirect-state' + }) + + t.teardown(fastify.close.bind(fastify)) + + fastify.inject( + { + method: 'GET', + url: '/login' + }, + function (err, responseEnd) { + t.error(err) + + t.equal(responseEnd.statusCode, 302) + t.matchStrict(responseEnd.cookies[0].name, 'custom-redirect-state') + t.matchStrict(responseEnd.cookies[0].value, String) + + t.end() + } + ) + }) +}) + +t.test('options.verifierCookieName', (t) => { + t.plan(2) + + t.test('should be a string', (t) => { + t.plan(1) + + const fastify = createFastify({ logger: { level: 'silent' } }) + + fastify + .register(fastifyOauth2, { + name: 'the-name', + credentials: { + client: { + id: 'my-client-id', + secret: 'my-secret' + }, + auth: fastifyOauth2.GITHUB_CONFIGURATION + }, + callbackUri: '/callback', + verifierCookieName: 42 + }) + .ready((err) => { + t.strictSame( + err.message, + 'options.verifierCookieName should be a string' + ) + }) + }) + + t.test('with custom cookie name', (t) => { + t.plan(4) + + const fastify = createFastify({ logger: { level: 'silent' } }) + + fastify.register(fastifyOauth2, { + name: 'the-name', + credentials: { + client: { + id: 'my-client-id', + secret: 'my-secret' + }, + auth: fastifyOauth2.GITHUB_CONFIGURATION + }, + callbackUri: '/callback', + startRedirectPath: '/login', + verifierCookieName: 'custom-verifier', + pkce: 'plain' + }) + + t.teardown(fastify.close.bind(fastify)) + + fastify.inject( + { + method: 'GET', + url: '/login' + }, + function (err, responseEnd) { + t.error(err) + + t.equal(responseEnd.statusCode, 302) + t.matchStrict(responseEnd.cookies[1].name, 'custom-verifier') + t.matchStrict(responseEnd.cookies[1].value, String) + + t.end() + } + ) + }) +}) diff --git a/types/index.d.ts b/types/index.d.ts index f5084ac..ab01b48 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -46,6 +46,8 @@ declare namespace fastifyOauth2 { userAgent?: string | false; pkce?: 'S256' | 'plain'; discovery?: { issuer: string; } + redirectStateCookieName?: string; + verifierCookieName?: string; } export type TToken = 'access_token' | 'refresh_token' diff --git a/types/index.test-d.ts b/types/index.test-d.ts index 7fc67a5..09062f1 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -58,6 +58,8 @@ const OAuth2Options: FastifyOAuth2Options = { secure: true, sameSite: 'none' }, + redirectStateCookieName: 'redirect-state-cookie', + verifierCookieName: 'verifier-cookie', }; expectAssignable({ From b7826ddf99048c2a9e0c2faf95be8814201934c5 Mon Sep 17 00:00:00 2001 From: Vladimir Tkac Date: Mon, 23 Sep 2024 12:55:16 +0200 Subject: [PATCH 2/2] Apply PR code suggestions --- README.md | 11 +++++++++++ index.js | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 14859cf..93bfb14 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,17 @@ fastify.register(oauthPlugin, { }) ``` +Additionally, you can customize the names of the cookies by setting the `redirectStateCookieName` and `verifierCookieName` options. +The default values for these cookies are `oauth2-code-verifier` for `verifierCookieName` and `oauth2-redirect-state` for `redirectStateCookieName`. + +```js +fastify.register(oauthPlugin, { + ..., + redirectStateCookieName: 'custom-redirect-state', + verifierCookieName: 'custom-code-verifier' +}) +``` + ### Preset configurations You can choose some default setup to assign to `auth` option. diff --git a/index.js b/index.js index 696fef5..dcac1f2 100644 --- a/index.js +++ b/index.js @@ -28,7 +28,7 @@ function defaultCheckStateFunction (request, callback) { const state = request.query.state const stateCookie = request.cookies[ - this.redirectStateCookieName || DEFAULT_REDIRECT_STATE_COOKIE_NAME + this.redirectStateCookieName ] if (stateCookie && state === stateCookie) { callback() @@ -135,7 +135,9 @@ function fastifyOauth2 (fastify, options, next) { scope, generateStateFunction = defaultGenerateStateFunction, checkStateFunction = defaultCheckStateFunction.bind({ - redirectStateCookieName: configured.redirectStateCookieName + redirectStateCookieName: + configured.redirectStateCookieName || + DEFAULT_REDIRECT_STATE_COOKIE_NAME }), startRedirectPath, tags = [],