From 6d4cde4b02a799cda0c91c226a5f56fb7e9248b9 Mon Sep 17 00:00:00 2001 From: "${Mr.DJA}" <42304709+iMrDJAi@users.noreply.github.com> Date: Tue, 10 Jan 2023 13:11:52 +0100 Subject: [PATCH] feat(core): allow clearing cookies from `jwt()` (#6337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(core): allow clearing cookies from `jwt()` * revert: allow clearing cookies from `jwt()` * feat(core): re-apply changes against `@auth/core` * revert: `decodeJWT` option * doc: `jwt()` callback * Apply suggestions from code review Co-authored-by: Balázs Orbán --- packages/core/src/lib/routes/callback.ts | 77 ++++++++++++++---------- packages/core/src/lib/routes/session.ts | 37 +++++++----- packages/core/src/types.ts | 7 ++- 3 files changed, 73 insertions(+), 48 deletions(-) diff --git a/packages/core/src/lib/routes/callback.ts b/packages/core/src/lib/routes/callback.ts index 1370accc4f..35e33be51b 100644 --- a/packages/core/src/lib/routes/callback.ts +++ b/packages/core/src/lib/routes/callback.ts @@ -111,17 +111,22 @@ export async function callback(params: { isNewUser, }) - // Encode token - const newToken = await jwt.encode({ ...jwt, token }) - - // Set cookie expiry date - const cookieExpires = new Date() - cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) - - const sessionCookies = sessionStore.chunk(newToken, { - expires: cookieExpires, - }) - cookies.push(...sessionCookies) + // Clear cookies if token is null + if (token === null) { + cookies.push(...sessionStore.clean()) + } else { + // Encode token + const newToken = await jwt.encode({ ...jwt, token }) + + // Set cookie expiry date + const cookieExpires = new Date() + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) + + const sessionCookies = sessionStore.chunk(newToken, { + expires: cookieExpires, + }) + cookies.push(...sessionCookies) + } } else { // Save Session Token in cookie cookies.push({ @@ -214,17 +219,22 @@ export async function callback(params: { isNewUser, }) - // Encode token - const newToken = await jwt.encode({ ...jwt, token }) - - // Set cookie expiry date - const cookieExpires = new Date() - cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) - - const sessionCookies = sessionStore.chunk(newToken, { - expires: cookieExpires, - }) - cookies.push(...sessionCookies) + // Clear cookies if token is null + if (token === null) { + cookies.push(...sessionStore.clean()) + } else { + // Encode token + const newToken = await jwt.encode({ ...jwt, token }) + + // Set cookie expiry date + const cookieExpires = new Date() + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) + + const sessionCookies = sessionStore.chunk(newToken, { + expires: cookieExpires, + }) + cookies.push(...sessionCookies) + } } else { // Save Session Token in cookie cookies.push({ @@ -305,18 +315,23 @@ export async function callback(params: { isNewUser: false, }) - // Encode token - const newToken = await jwt.encode({ ...jwt, token }) + // Clear cookies if token is null + if (token === null) { + cookies.push(...sessionStore.clean()) + } else { + // Encode token + const newToken = await jwt.encode({ ...jwt, token }) - // Set cookie expiry date - const cookieExpires = new Date() - cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) + // Set cookie expiry date + const cookieExpires = new Date() + cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000) - const sessionCookies = sessionStore.chunk(newToken, { - expires: cookieExpires, - }) + const sessionCookies = sessionStore.chunk(newToken, { + expires: cookieExpires, + }) - cookies.push(...sessionCookies) + cookies.push(...sessionCookies) + } // @ts-expect-error await events.signIn?.({ user, account }) diff --git a/packages/core/src/lib/routes/session.ts b/packages/core/src/lib/routes/session.ts index d0efb685ba..c185ef7268 100644 --- a/packages/core/src/lib/routes/session.ts +++ b/packages/core/src/lib/routes/session.ts @@ -48,27 +48,32 @@ export async function session( // @ts-expect-error const token = await callbacks.jwt({ token: decodedToken }) - // @ts-expect-error - const newSession = await callbacks.session({ session, token }) - // Return session payload as response - response.body = newSession + if (token !== null) { + // @ts-expect-error + const newSession = await callbacks.session({ session, token }) - // Refresh JWT expiry by re-signing it, with an updated expiry date - const newToken = await jwt.encode({ - ...jwt, - token, - maxAge: options.session.maxAge, - }) + // Return session payload as response + response.body = newSession - // Set cookie, to also update expiry date on cookie - const sessionCookies = sessionStore.chunk(newToken, { - expires: newExpires, - }) + // Refresh JWT expiry by re-signing it, with an updated expiry date + const newToken = await jwt.encode({ + ...jwt, + token, + maxAge: options.session.maxAge, + }) + + // Set cookie, to also update expiry date on cookie + const sessionCookies = sessionStore.chunk(newToken, { + expires: newExpires, + }) - response.cookies?.push(...sessionCookies) + response.cookies?.push(...sessionCookies) - await events.session?.({ session: newSession, token }) + await events.session?.({ session: newSession, token }) + } else { + response.cookies?.push(...sessionStore.clean()) + } } catch (e) { logger.error(new JWTSessionError(e as Error)) // If the JWT is not verifiable remove the broken session cookie(s). diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 9e54e85d6f..dd37d09ee0 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -203,6 +203,11 @@ export interface CallbacksOptions

{ * Its content is forwarded to the `session` callback, * where you can control what should be returned to the client. * Anything else will be kept inaccessible from the client. + * + * Returning `null` will invalidate the JWT session by clearing + * the user's cookies. You'll still have to monitor and invalidate + * unexpired tokens from future requests yourself to prevent + * unauthorized access. * * By default the JWT is encrypted. * @@ -215,7 +220,7 @@ export interface CallbacksOptions

{ account?: A | null profile?: P isNewUser?: boolean - }) => Awaitable + }) => Awaitable } /** [Documentation](https://authjs.dev/reference/configuration/auth-config#cookies) */