From 5ac5bb257e350271451c2d5e8cfb19a1f7f4f100 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Mon, 5 May 2025 16:52:06 -0300 Subject: [PATCH 1/3] Update `toAuth` to handle pending status as signed-out --- packages/backend/src/tokens/authObjects.ts | 10 +++++++--- packages/backend/src/tokens/authStatus.ts | 13 ++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts index df0837eb19d..2b7ba218506 100644 --- a/packages/backend/src/tokens/authObjects.ts +++ b/packages/backend/src/tokens/authObjects.ts @@ -5,6 +5,7 @@ import type { JwtPayload, ServerGetToken, ServerGetTokenOptions, + SessionStatusClaim, SharedSignedInAuthObjectProperties, } from '@clerk/types'; @@ -37,7 +38,7 @@ export type SignedInAuthObject = SharedSignedInAuthObjectProperties & { export type SignedOutAuthObject = { sessionClaims: null; sessionId: null; - sessionStatus: null; + sessionStatus: SessionStatusClaim | null; actor: null; userId: null; orgId: null; @@ -113,11 +114,14 @@ export function signedInAuthObject( /** * @internal */ -export function signedOutAuthObject(debugData?: AuthObjectDebugData): SignedOutAuthObject { +export function signedOutAuthObject( + debugData?: AuthObjectDebugData, + initialSessionStatus?: SessionStatusClaim, +): SignedOutAuthObject { return { sessionClaims: null, sessionId: null, - sessionStatus: null, + sessionStatus: initialSessionStatus ?? null, userId: null, actor: null, orgId: null, diff --git a/packages/backend/src/tokens/authStatus.ts b/packages/backend/src/tokens/authStatus.ts index 0d73f3e2539..cf1524bae2e 100644 --- a/packages/backend/src/tokens/authStatus.ts +++ b/packages/backend/src/tokens/authStatus.ts @@ -1,4 +1,4 @@ -import type { JwtPayload } from '@clerk/types'; +import type { JwtPayload, PendingSessionOptions } from '@clerk/types'; import { constants } from '../constants'; import type { TokenVerificationErrorReason } from '../errors'; @@ -27,7 +27,7 @@ export type SignedInState = { afterSignInUrl: string; afterSignUpUrl: string; isSignedIn: true; - toAuth: () => SignedInAuthObject; + toAuth: (opts?: PendingSessionOptions) => SignedInAuthObject; headers: Headers; token: string; }; @@ -99,7 +99,14 @@ export function signedIn( afterSignInUrl: authenticateContext.afterSignInUrl || '', afterSignUpUrl: authenticateContext.afterSignUpUrl || '', isSignedIn: true, - toAuth: () => authObject, + // @ts-expect-error Dynamically return `SignedOutAuthObject` based on options + toAuth: ({ treatPendingAsSignedOut = true } = {}) => { + if (treatPendingAsSignedOut && authObject.sessionStatus === 'pending') { + return signedOutAuthObject(undefined, authObject.sessionStatus); + } + + return authObject; + }, headers, token, }; From a7343bbc17d92c6e435ec2e2502f4d05fa5bc844 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Mon, 5 May 2025 16:52:18 -0300 Subject: [PATCH 2/3] Forward options to `getAuth` --- packages/express/src/authenticateRequest.ts | 2 +- packages/express/src/getAuth.ts | 9 +++++++-- packages/express/src/requireAuth.ts | 2 +- packages/express/src/types.ts | 3 ++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/express/src/authenticateRequest.ts b/packages/express/src/authenticateRequest.ts index 827594e2691..8ca89e94300 100644 --- a/packages/express/src/authenticateRequest.ts +++ b/packages/express/src/authenticateRequest.ts @@ -110,7 +110,7 @@ export const authenticateAndDecorateRequest = (options: ClerkMiddlewareOptions = } } - const auth = requestState.toAuth(); + const auth = (opts: Parameters[0]) => requestState.toAuth(opts); Object.assign(request, { auth }); next(); diff --git a/packages/express/src/getAuth.ts b/packages/express/src/getAuth.ts index 0e1f1cb65af..d3da632d267 100644 --- a/packages/express/src/getAuth.ts +++ b/packages/express/src/getAuth.ts @@ -1,20 +1,25 @@ import type { AuthObject } from '@clerk/backend'; +import type { PendingSessionOptions } from '@clerk/types'; import type { Request as ExpressRequest } from 'express'; import { middlewareRequired } from './errors'; import { requestHasAuthObject } from './utils'; +type GetAuthOptions = PendingSessionOptions; + /** * Retrieves the Clerk AuthObject using the current request object. * * @param {ExpressRequest} req - The current request object. + * @param {GetAuthOptions} options - Optional configuration for retriving auth object. * @returns {AuthObject} Object with information about the request state and claims. * @throws {Error} `clerkMiddleware` or `requireAuth` is required to be set in the middleware chain before this util is used. */ -export const getAuth = (req: ExpressRequest): AuthObject => { +export const getAuth = (req: ExpressRequest, options?: GetAuthOptions): AuthObject => { if (!requestHasAuthObject(req)) { throw new Error(middlewareRequired('getAuth')); } - return req.auth; + // this has to receive an option + return req.auth(options); }; diff --git a/packages/express/src/requireAuth.ts b/packages/express/src/requireAuth.ts index e2b8f666a3a..c1ad5a7c353 100644 --- a/packages/express/src/requireAuth.ts +++ b/packages/express/src/requireAuth.ts @@ -43,7 +43,7 @@ export const requireAuth = (options: ClerkMiddlewareOptions = {}): RequestHandle const signInUrl = options.signInUrl || process.env.CLERK_SIGN_IN_URL || '/'; - if (!(request as ExpressRequestWithAuth).auth?.userId) { + if (!(request as ExpressRequestWithAuth).auth()?.userId) { return response.redirect(signInUrl); } diff --git a/packages/express/src/types.ts b/packages/express/src/types.ts index c8167928fff..0cb4d07c771 100644 --- a/packages/express/src/types.ts +++ b/packages/express/src/types.ts @@ -1,8 +1,9 @@ import type { AuthObject, createClerkClient } from '@clerk/backend'; import type { AuthenticateRequestOptions } from '@clerk/backend/internal'; +import type { PendingSessionOptions } from '@clerk/types'; import type { Request as ExpressRequest } from 'express'; -export type ExpressRequestWithAuth = ExpressRequest & { auth: AuthObject }; +export type ExpressRequestWithAuth = ExpressRequest & { auth: (options?: PendingSessionOptions) => AuthObject }; export type ClerkMiddlewareOptions = AuthenticateRequestOptions & { debug?: boolean; From 17c8480f652c09644a3797d1fe9e68a375dd45e9 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Mon, 5 May 2025 16:53:57 -0300 Subject: [PATCH 3/3] Add changeset --- .changeset/flat-papers-begin.md | 16 ++++++++++++++++ packages/backend/src/tokens/authStatus.ts | 2 +- packages/express/src/__tests__/helpers.ts | 4 ++-- .../express/src/__tests__/requireAuth.test.ts | 2 +- packages/express/src/getAuth.ts | 1 - 5 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 .changeset/flat-papers-begin.md diff --git a/.changeset/flat-papers-begin.md b/.changeset/flat-papers-begin.md new file mode 100644 index 00000000000..d7335378fe9 --- /dev/null +++ b/.changeset/flat-papers-begin.md @@ -0,0 +1,16 @@ +--- +'@clerk/backend': minor +'@clerk/express': minor +--- + +Introduce `treatPendingAsSignedOut` option to `getAuth` + +```ts +// `pending` sessions will be treated as signed-out by default +const { userId } = getAuth(req) +``` + +```ts +// Both `active` and `pending` sessions will be treated as authenticated when `treatPendingAsSignedOut` is false +const { userId } = getAuth(req, { treatPendingAsSignedOut: false }) +``` diff --git a/packages/backend/src/tokens/authStatus.ts b/packages/backend/src/tokens/authStatus.ts index cf1524bae2e..55c29f7352b 100644 --- a/packages/backend/src/tokens/authStatus.ts +++ b/packages/backend/src/tokens/authStatus.ts @@ -99,7 +99,7 @@ export function signedIn( afterSignInUrl: authenticateContext.afterSignInUrl || '', afterSignUpUrl: authenticateContext.afterSignUpUrl || '', isSignedIn: true, - // @ts-expect-error Dynamically return `SignedOutAuthObject` based on options + // @ts-expect-error The return type is intentionally overridden here to support consumer-facing logic that treats pending sessions as signed out. This override does not affect internal session management like handshake flows. toAuth: ({ treatPendingAsSignedOut = true } = {}) => { if (treatPendingAsSignedOut && authObject.sessionStatus === 'pending') { return signedOutAuthObject(undefined, authObject.sessionStatus); diff --git a/packages/express/src/__tests__/helpers.ts b/packages/express/src/__tests__/helpers.ts index 33627a5bc52..98cb848ce0b 100644 --- a/packages/express/src/__tests__/helpers.ts +++ b/packages/express/src/__tests__/helpers.ts @@ -28,7 +28,7 @@ export function mockRequest(): ExpressRequest { export function mockRequestWithAuth(auth: Partial = {}): ExpressRequestWithAuth { return { - auth: { + auth: () => ({ sessionClaims: null, sessionId: null, actor: null, @@ -41,7 +41,7 @@ export function mockRequestWithAuth(auth: Partial = {}): ExpressRequ has: () => false, debug: () => ({}), ...auth, - }, + }), } as unknown as ExpressRequestWithAuth; } diff --git a/packages/express/src/__tests__/requireAuth.test.ts b/packages/express/src/__tests__/requireAuth.test.ts index faa3e8d72bf..5bfb705a67b 100644 --- a/packages/express/src/__tests__/requireAuth.test.ts +++ b/packages/express/src/__tests__/requireAuth.test.ts @@ -81,7 +81,7 @@ describe('requireAuth', () => { return next(); } const requestState = mockAuthenticateRequest({ request: req }); - Object.assign(req, { auth: requestState.toAuth() }); + Object.assign(req, { auth: () => requestState.toAuth() }); next(); }; }); diff --git a/packages/express/src/getAuth.ts b/packages/express/src/getAuth.ts index d3da632d267..29ba37ad478 100644 --- a/packages/express/src/getAuth.ts +++ b/packages/express/src/getAuth.ts @@ -20,6 +20,5 @@ export const getAuth = (req: ExpressRequest, options?: GetAuthOptions): AuthObje throw new Error(middlewareRequired('getAuth')); } - // this has to receive an option return req.auth(options); };