From dae043afb1f9649d63fd155d5c025689527af4d1 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 13 Jun 2025 05:40:48 -0700 Subject: [PATCH 1/8] chore: handle intent based token types --- .../backend/src/__tests__/exports.test.ts | 3 +- packages/backend/src/internal.ts | 2 +- .../src/tokens/__tests__/authObjects.test.ts | 39 +++++ .../src/tokens/__tests__/machine.test.ts | 29 +++- packages/backend/src/tokens/authObjects.ts | 57 +++---- packages/backend/src/tokens/machine.ts | 12 +- packages/backend/src/tokens/request.ts | 6 +- .../__tests__/getAuthDataFromRequest.test.ts | 42 ++++- packages/nextjs/src/server/clerkMiddleware.ts | 4 +- .../src/server/data/getAuthDataFromRequest.ts | 147 ++++++++++++++---- 10 files changed, 256 insertions(+), 85 deletions(-) diff --git a/packages/backend/src/__tests__/exports.test.ts b/packages/backend/src/__tests__/exports.test.ts index a9fa7faf256..d597203dd7c 100644 --- a/packages/backend/src/__tests__/exports.test.ts +++ b/packages/backend/src/__tests__/exports.test.ts @@ -50,7 +50,8 @@ describe('subpath /internal exports', () => { "getAuthObjectForAcceptedToken", "getAuthObjectFromJwt", "getMachineTokenType", - "isMachineToken", + "isMachineTokenByPrefix", + "isMachineTokenType", "isTokenTypeAccepted", "makeAuthObjectSerializable", "reverificationError", diff --git a/packages/backend/src/internal.ts b/packages/backend/src/internal.ts index 4f176cc0489..02a86836949 100644 --- a/packages/backend/src/internal.ts +++ b/packages/backend/src/internal.ts @@ -53,4 +53,4 @@ export { reverificationError, reverificationErrorResponse } from '@clerk/shared/ export { verifyMachineAuthToken } from './tokens/verify'; -export { isMachineToken, getMachineTokenType, isTokenTypeAccepted } from './tokens/machine'; +export { isMachineTokenByPrefix, isMachineTokenType, getMachineTokenType, isTokenTypeAccepted } from './tokens/machine'; diff --git a/packages/backend/src/tokens/__tests__/authObjects.test.ts b/packages/backend/src/tokens/__tests__/authObjects.test.ts index 55b162bc025..758fa01f235 100644 --- a/packages/backend/src/tokens/__tests__/authObjects.test.ts +++ b/packages/backend/src/tokens/__tests__/authObjects.test.ts @@ -3,8 +3,10 @@ import { describe, expect, it } from 'vitest'; import { mockTokens, mockVerificationResults } from '../../fixtures/machine'; import type { AuthenticateContext } from '../authenticateContext'; +import type { SignedOutAuthObject, UnauthenticatedMachineObject } from '../authObjects'; import { authenticatedMachineObject, + getAuthObjectForAcceptedToken, makeAuthObjectSerializable, signedInAuthObject, signedOutAuthObject, @@ -387,3 +389,40 @@ describe('unauthenticatedMachineObject', () => { expect(retrievedToken).toBeNull(); }); }); + +describe('getAuthObjectForAcceptedToken', () => { + const debugData = { foo: 'bar' }; + const sessionAuth = signedOutAuthObject(debugData); + const machineAuth = authenticatedMachineObject('api_key', 'ak_xxx', mockVerificationResults.api_key, debugData); + + it('returns original object if acceptsToken is "any"', () => { + const result = getAuthObjectForAcceptedToken({ authObject: machineAuth, acceptsToken: 'any' }); + expect(result).toBe(machineAuth); + }); + + it('returns original object if token type matches', () => { + const result = getAuthObjectForAcceptedToken({ authObject: machineAuth, acceptsToken: 'api_key' }); + expect(result).toBe(machineAuth); + }); + + it('returns unauthenticated machine object for parsed type if acceptsToken is array and token type does not match', () => { + const result = getAuthObjectForAcceptedToken({ + authObject: machineAuth, + acceptsToken: ['machine_token', 'oauth_token'], + }); + expect((result as UnauthenticatedMachineObject<'api_key'>).tokenType).toBe('api_key'); + expect((result as UnauthenticatedMachineObject<'api_key'>).id).toBeNull(); + }); + + it('returns signed-out session object if parsed type is not a machine token and does not match', () => { + const result = getAuthObjectForAcceptedToken({ authObject: sessionAuth, acceptsToken: ['api_key', 'oauth_token'] }); + expect((result as SignedOutAuthObject).tokenType).toBe('session_token'); + expect((result as SignedOutAuthObject).userId).toBeNull(); + }); + + it('returns unauthenticated object for requested type if acceptsToken is a single value and does not match', () => { + const result = getAuthObjectForAcceptedToken({ authObject: machineAuth, acceptsToken: 'machine_token' }); + expect((result as UnauthenticatedMachineObject<'machine_token'>).tokenType).toBe('machine_token'); + expect((result as UnauthenticatedMachineObject<'machine_token'>).id).toBeNull(); + }); +}); diff --git a/packages/backend/src/tokens/__tests__/machine.test.ts b/packages/backend/src/tokens/__tests__/machine.test.ts index dd84e40d3bb..fcff9e566b2 100644 --- a/packages/backend/src/tokens/__tests__/machine.test.ts +++ b/packages/backend/src/tokens/__tests__/machine.test.ts @@ -3,7 +3,8 @@ import { describe, expect, it } from 'vitest'; import { API_KEY_PREFIX, getMachineTokenType, - isMachineToken, + isMachineTokenByPrefix, + isMachineTokenType, isTokenTypeAccepted, M2M_TOKEN_PREFIX, OAUTH_TOKEN_PREFIX, @@ -11,25 +12,25 @@ import { describe('isMachineToken', () => { it('returns true for tokens with M2M prefix', () => { - expect(isMachineToken(`${M2M_TOKEN_PREFIX}some-token-value`)).toBe(true); + expect(isMachineTokenByPrefix(`${M2M_TOKEN_PREFIX}some-token-value`)).toBe(true); }); it('returns true for tokens with OAuth prefix', () => { - expect(isMachineToken(`${OAUTH_TOKEN_PREFIX}some-token-value`)).toBe(true); + expect(isMachineTokenByPrefix(`${OAUTH_TOKEN_PREFIX}some-token-value`)).toBe(true); }); it('returns true for tokens with API key prefix', () => { - expect(isMachineToken(`${API_KEY_PREFIX}some-token-value`)).toBe(true); + expect(isMachineTokenByPrefix(`${API_KEY_PREFIX}some-token-value`)).toBe(true); }); it('returns false for tokens without a recognized prefix', () => { - expect(isMachineToken('unknown_prefix_token')).toBe(false); - expect(isMachineToken('session_token_value')).toBe(false); - expect(isMachineToken('jwt_token_value')).toBe(false); + expect(isMachineTokenByPrefix('unknown_prefix_token')).toBe(false); + expect(isMachineTokenByPrefix('session_token_value')).toBe(false); + expect(isMachineTokenByPrefix('jwt_token_value')).toBe(false); }); it('returns false for empty tokens', () => { - expect(isMachineToken('')).toBe(false); + expect(isMachineTokenByPrefix('')).toBe(false); }); }); @@ -78,3 +79,15 @@ describe('isTokenTypeAccepted', () => { expect(isTokenTypeAccepted('api_key', 'machine_token')).toBe(false); }); }); + +describe('isMachineTokenType', () => { + it('returns true for machine token types', () => { + expect(isMachineTokenType('api_key')).toBe(true); + expect(isMachineTokenType('machine_token')).toBe(true); + expect(isMachineTokenType('oauth_token')).toBe(true); + }); + + it('returns false for non-machine token types', () => { + expect(isMachineTokenType('session_token')).toBe(false); + }); +}); diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts index a876fddbd03..4596c2578bd 100644 --- a/packages/backend/src/tokens/authObjects.ts +++ b/packages/backend/src/tokens/authObjects.ts @@ -15,6 +15,7 @@ import type { APIKey, CreateBackendApiOptions, IdPOAuthAccessToken, MachineToken import { createBackendApiClient } from '../api'; import { isTokenTypeAccepted } from '../internal'; import type { AuthenticateContext } from './authenticateContext'; +import { isMachineTokenType } from './machine'; import type { MachineTokenType, SessionTokenType } from './tokenTypes'; import { TokenType } from './tokenTypes'; import type { AuthenticateRequestOptions, MachineAuthType } from './types'; @@ -394,42 +395,13 @@ export const getAuthObjectFromJwt = ( /** * @internal - * Filters and coerces an AuthObject based on the accepted token type(s). + * Returns an auth object matching the requested token type(s). * - * This function is used after authentication to ensure that the returned auth object - * matches the expected token type(s) specified by `acceptsToken`. If the token type - * of the provided `authObject` does not match any of the types in `acceptsToken`, - * it returns an unauthenticated or signed-out version of the object, depending on the token type. + * If the parsed token type does not match any in acceptsToken, returns: + * - an unauthenticated machine object for the first machine token type in acceptsToken (if present), or + * - a signed-out session object otherwise. * - * - If `acceptsToken` is `'any'`, the original auth object is returned. - * - If `acceptsToken` is a single token type or an array of token types, the function checks if - * `authObject.tokenType` matches any of them. - * - If the token type does not match and is a session token, a signed-out object is returned. - * - If the token type does not match and is a machine token, an unauthenticated machine object is returned. - * - If the token type matches, the original auth object is returned. - * - * @param {Object} params - * @param {AuthObject} params.authObject - The authenticated object to filter. - * @param {AuthenticateRequestOptions['acceptsToken']} [params.acceptsToken=TokenType.SessionToken] - The accepted token type(s). Can be a string, array of strings, or 'any'. - * @returns {AuthObject} The filtered or coerced auth object. - * - * @example - * // Accept only 'api_key' tokens - * const authObject = { tokenType: 'session_token', userId: 'user_123' }; - * const result = getAuthObjectForAcceptedToken({ authObject, acceptsToken: 'api_key' }); - * // result will be a signed-out object (since tokenType is 'session_token' and does not match) - * - * @example - * // Accept 'api_key' or 'machine_token' - * const authObject = { tokenType: 'machine_token', id: 'm2m_123' }; - * const result = getAuthObjectForAcceptedToken({ authObject, acceptsToken: ['api_key', 'machine_token'] }); - * // result will be the original authObject (since tokenType matches one in the array) - * - * @example - * // Accept any token type - * const authObject = { tokenType: 'api_key', id: 'ak_123' }; - * const result = getAuthObjectForAcceptedToken({ authObject, acceptsToken: 'any' }); - * // result will be the original authObject + * This ensures the returned object always matches the developer's intent. */ export function getAuthObjectForAcceptedToken({ authObject, @@ -442,11 +414,22 @@ export function getAuthObjectForAcceptedToken({ return authObject; } - if (!isTokenTypeAccepted(authObject.tokenType, acceptsToken)) { - if (authObject.tokenType === TokenType.SessionToken) { + if (Array.isArray(acceptsToken)) { + if (!isTokenTypeAccepted(authObject.tokenType, acceptsToken)) { + if (isMachineTokenType(authObject.tokenType)) { + return unauthenticatedMachineObject(authObject.tokenType, authObject.debug); + } return signedOutAuthObject(authObject.debug); } - return unauthenticatedMachineObject(authObject.tokenType, authObject.debug); + return authObject; + } + + // Single value: Intent based + if (!isTokenTypeAccepted(authObject.tokenType, acceptsToken)) { + if (isMachineTokenType(acceptsToken)) { + return unauthenticatedMachineObject(acceptsToken, authObject.debug); + } + return signedOutAuthObject(authObject.debug); } return authObject; diff --git a/packages/backend/src/tokens/machine.ts b/packages/backend/src/tokens/machine.ts index 33ad67e21f9..6be97d75103 100644 --- a/packages/backend/src/tokens/machine.ts +++ b/packages/backend/src/tokens/machine.ts @@ -18,7 +18,7 @@ const MACHINE_TOKEN_PREFIXES = [M2M_TOKEN_PREFIX, OAUTH_TOKEN_PREFIX, API_KEY_PR * @param token - The token string to check * @returns true if the token starts with a recognized machine token prefix */ -export function isMachineToken(token: string): boolean { +export function isMachineTokenByPrefix(token: string): boolean { return MACHINE_TOKEN_PREFIXES.some(prefix => token.startsWith(prefix)); } @@ -67,3 +67,13 @@ export const isTokenTypeAccepted = ( const tokenTypes = Array.isArray(acceptsToken) ? acceptsToken : [acceptsToken]; return tokenTypes.includes(tokenType); }; + +/** + * Checks if a token type string is a machine token type (api_key, machine_token, or oauth_token). + * + * @param type - The token type string to check + * @returns true if the type is a machine token type + */ +export function isMachineTokenType(type: string): type is MachineTokenType { + return type === TokenType.ApiKey || type === TokenType.MachineToken || type === TokenType.OAuthToken; +} diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts index 8b9143c1487..9b7e5f6c12a 100644 --- a/packages/backend/src/tokens/request.ts +++ b/packages/backend/src/tokens/request.ts @@ -14,7 +14,7 @@ import { AuthErrorReason, handshake, signedIn, signedOut } from './authStatus'; import { createClerkRequest } from './clerkRequest'; import { getCookieName, getCookieValue } from './cookie'; import { HandshakeService } from './handshake'; -import { getMachineTokenType, isMachineToken, isTokenTypeAccepted } from './machine'; +import { getMachineTokenType, isMachineTokenByPrefix, isTokenTypeAccepted } from './machine'; import { OrganizationMatcher } from './organizationMatcher'; import type { MachineTokenType, SessionTokenType } from './tokenTypes'; import { TokenType } from './tokenTypes'; @@ -653,7 +653,7 @@ export const authenticateRequest: AuthenticateRequest = (async ( } // Handle case where tokenType is any and the token is not a machine token - if (!isMachineToken(tokenInHeader)) { + if (!isMachineTokenByPrefix(tokenInHeader)) { return signedOut({ tokenType: acceptsToken as MachineTokenType, authenticateContext, @@ -688,7 +688,7 @@ export const authenticateRequest: AuthenticateRequest = (async ( } // Handle as a machine token - if (isMachineToken(tokenInHeader)) { + if (isMachineTokenByPrefix(tokenInHeader)) { const parsedTokenType = getMachineTokenType(tokenInHeader); const mismatchState = checkTokenTypeMismatch(parsedTokenType, acceptsToken, authenticateContext); if (mismatchState) { diff --git a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts index 17a2d967a35..09e80594c75 100644 --- a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts +++ b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts @@ -42,8 +42,46 @@ describe('getAuthDataFromRequestAsync', () => { acceptsToken: 'machine_token', }); + expect(auth.tokenType).toBe('machine_token'); + expect((auth as AuthenticatedMachineObject<'machine_token'>).machineId).toBeNull(); + }); + + it('returns unauthenticated machine object for the actual parsed machine token type when token type does not match any in acceptsToken array', async () => { + const req = mockRequest({ + url: '/api/protected', + headers: new Headers({ + [constants.Headers.Authorization]: 'Bearer ak_xxx', + }), + }); + + const auth = await getAuthDataFromRequestAsync(req, { + acceptsToken: ['machine_token', 'oauth_token', 'session_token'], + }); + + expect(auth.tokenType).toBe('api_key'); + expect((auth as AuthenticatedMachineObject<'api_key'>).userId).toBeNull(); + }); + + it('returns authenticated api_key object when array contains only api_key and token is ak_xxx and verification passes', async () => { + vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({ + data: { id: 'ak_123', subject: 'user_12345' } as any, + tokenType: 'api_key', + errors: undefined, + }); + + const req = mockRequest({ + url: '/api/protected', + headers: new Headers({ + [constants.Headers.Authorization]: 'Bearer ak_xxx', + }), + }); + + const auth = await getAuthDataFromRequestAsync(req, { + acceptsToken: ['api_key'], + }); + expect(auth.tokenType).toBe('api_key'); - expect((auth as AuthenticatedMachineObject).id).toBeNull(); + expect((auth as AuthenticatedMachineObject<'api_key'>).id).toBe('ak_123'); }); it('returns authenticated machine object when token type matches', async () => { @@ -65,7 +103,7 @@ describe('getAuthDataFromRequestAsync', () => { }); expect(auth.tokenType).toBe('api_key'); - expect((auth as AuthenticatedMachineObject).id).toBe('ak_123'); + expect((auth as AuthenticatedMachineObject<'api_key'>).id).toBe('ak_123'); }); it('falls back to session token handling', async () => { diff --git a/packages/nextjs/src/server/clerkMiddleware.ts b/packages/nextjs/src/server/clerkMiddleware.ts index 9dd32a2d49b..f698399410e 100644 --- a/packages/nextjs/src/server/clerkMiddleware.ts +++ b/packages/nextjs/src/server/clerkMiddleware.ts @@ -12,7 +12,7 @@ import { constants, createClerkRequest, createRedirect, - isMachineToken, + isMachineTokenByPrefix, isTokenTypeAccepted, signedOutAuthObject, TokenType, @@ -288,7 +288,7 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl /** * In keyless mode, if the publishable key is missing, let the request through, to render `` that will resume the flow gracefully. */ - if (isMissingPublishableKey && !isMachineToken(authHeader)) { + if (isMissingPublishableKey && !isMachineTokenByPrefix(authHeader)) { const res = NextResponse.next(); setRequestHeadersOnNextResponse(res, request, { [constants.Headers.AuthStatus]: 'signed-out', diff --git a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts index d5cd3039ceb..5f199dba3d5 100644 --- a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts +++ b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts @@ -1,13 +1,17 @@ import type { AuthObject } from '@clerk/backend'; -import type { AuthenticateRequestOptions, SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend/internal'; import { authenticatedMachineObject, + type AuthenticateRequestOptions, AuthStatus, constants, getAuthObjectFromJwt, getMachineTokenType, - isMachineToken, + isMachineTokenByPrefix, + isMachineTokenType, isTokenTypeAccepted, + type MachineTokenType, + type SignedInAuthObject, + type SignedOutAuthObject, signedOutAuthObject, TokenType, unauthenticatedMachineObject, @@ -85,46 +89,60 @@ export const getAuthDataFromRequestAsync = async ( opts: GetAuthDataFromRequestOptions = {}, ): Promise => { const { authStatus, authMessage, authReason } = getAuthHeaders(req); - opts.logger?.debug('headers', { authStatus, authMessage, authReason }); const bearerToken = getHeader(req, constants.Headers.Authorization)?.replace('Bearer ', ''); const acceptsToken = opts.acceptsToken || TokenType.SessionToken; + const options = { + secretKey: opts?.secretKey || SECRET_KEY, + publishableKey: PUBLISHABLE_KEY, + apiUrl: API_URL, + authStatus, + authMessage, + authReason, + }; - if (bearerToken && isMachineToken(bearerToken)) { - const tokenType = getMachineTokenType(bearerToken); - - const options = { - secretKey: opts?.secretKey || SECRET_KEY, - publishableKey: PUBLISHABLE_KEY, - apiUrl: API_URL, - authStatus, - authMessage, - authReason, - }; - - if (!isTokenTypeAccepted(tokenType, acceptsToken)) { - return unauthenticatedMachineObject(tokenType, options); - } - - // TODO(Rob): Cache the result of verifyMachineAuthToken - const { data, errors } = await verifyMachineAuthToken(bearerToken, options); - if (errors) { - return unauthenticatedMachineObject(tokenType, options); + if (bearerToken) { + const isMachine = isMachineTokenByPrefix(bearerToken); + const tokenType = isMachine ? getMachineTokenType(bearerToken) : undefined; + + if (Array.isArray(acceptsToken)) { + if (isMachine) { + if (!tokenType) { + return signedOutAuthObject(options); + } + return handleMachineToken({ bearerToken, tokenType, acceptsToken, options }); + } else { + return signedOutAuthObject(options); + } + } else { + let intendedType: TokenType | undefined; + if (isMachineTokenType(acceptsToken)) { + intendedType = acceptsToken; + } + const result = await handleIntentBased({ + isMachine, + tokenType, + intendedType, + bearerToken, + acceptsToken, + options, + }); + if (result) { + return result; + } } - - return authenticatedMachineObject(tokenType, bearerToken, data); } return getAuthDataFromRequestSync(req, opts); }; const getAuthHeaders = (req: RequestLike) => { - const authStatus = getAuthKeyFromRequest(req, 'AuthStatus'); - const authToken = getAuthKeyFromRequest(req, 'AuthToken'); - const authMessage = getAuthKeyFromRequest(req, 'AuthMessage'); - const authReason = getAuthKeyFromRequest(req, 'AuthReason'); - const authSignature = getAuthKeyFromRequest(req, 'AuthSignature'); + const authStatus = getAuthKeyFromRequest(req, 'AuthStatus') ?? null; + const authToken = getAuthKeyFromRequest(req, 'AuthToken') ?? null; + const authMessage = getAuthKeyFromRequest(req, 'AuthMessage') ?? null; + const authReason = getAuthKeyFromRequest(req, 'AuthReason') ?? null; + const authSignature = getAuthKeyFromRequest(req, 'AuthSignature') ?? null; return { authStatus, @@ -134,3 +152,72 @@ const getAuthHeaders = (req: RequestLike) => { authSignature, }; }; + +/** + * Handles verification and response shaping for machine tokens. + * Returns an authenticated or unauthenticated machine object based on verification and type acceptance. + */ +async function handleMachineToken({ + bearerToken, + tokenType, + acceptsToken, + options, +}: { + bearerToken: string; + tokenType: MachineTokenType; + acceptsToken: AuthenticateRequestOptions['acceptsToken']; + options: Record; +}) { + if (!tokenType) { + return signedOutAuthObject(options); + } + if (!isTokenTypeAccepted(tokenType, acceptsToken ?? TokenType.SessionToken)) { + return unauthenticatedMachineObject(tokenType, options); + } + const { data, errors } = await verifyMachineAuthToken(bearerToken, options); + if (errors) { + return unauthenticatedMachineObject(tokenType, options); + } + return authenticatedMachineObject(tokenType, bearerToken, data); +} + +/** + * Handles intent-based fallback for single-value acceptsToken. + * Returns an unauthenticated object for the intended type, or falls back to session logic if not applicable. + */ +async function handleIntentBased({ + isMachine, + tokenType, + intendedType, + bearerToken, + acceptsToken, + options, +}: { + isMachine: boolean; + tokenType: TokenType | undefined; + intendedType: TokenType | undefined; + bearerToken: string; + acceptsToken: AuthenticateRequestOptions['acceptsToken']; + options: Record; +}) { + if (isMachine) { + if (!tokenType) { + return signedOutAuthObject(options); + } + if (!isTokenTypeAccepted(tokenType, acceptsToken ?? TokenType.SessionToken)) { + if (intendedType && isMachineTokenType(intendedType)) { + return unauthenticatedMachineObject(intendedType as MachineTokenType, options); + } + return signedOutAuthObject(options); + } + const { data, errors } = await verifyMachineAuthToken(bearerToken, options); + if (errors) { + return unauthenticatedMachineObject(tokenType as MachineTokenType, options); + } + return authenticatedMachineObject(tokenType as MachineTokenType, bearerToken, data); + } else if (intendedType && isMachineTokenType(intendedType)) { + return unauthenticatedMachineObject(intendedType, options); + } + // else: fall through to session logic + return null; +} From 0d9d7aa621c5ab512db40746fcb003e9a366e3b0 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 13 Jun 2025 05:41:38 -0700 Subject: [PATCH 2/8] chore: keep header return --- .../nextjs/src/server/data/getAuthDataFromRequest.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts index 5f199dba3d5..771498245ee 100644 --- a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts +++ b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts @@ -138,11 +138,11 @@ export const getAuthDataFromRequestAsync = async ( }; const getAuthHeaders = (req: RequestLike) => { - const authStatus = getAuthKeyFromRequest(req, 'AuthStatus') ?? null; - const authToken = getAuthKeyFromRequest(req, 'AuthToken') ?? null; - const authMessage = getAuthKeyFromRequest(req, 'AuthMessage') ?? null; - const authReason = getAuthKeyFromRequest(req, 'AuthReason') ?? null; - const authSignature = getAuthKeyFromRequest(req, 'AuthSignature') ?? null; + const authStatus = getAuthKeyFromRequest(req, 'AuthStatus'); + const authToken = getAuthKeyFromRequest(req, 'AuthToken'); + const authMessage = getAuthKeyFromRequest(req, 'AuthMessage'); + const authReason = getAuthKeyFromRequest(req, 'AuthReason'); + const authSignature = getAuthKeyFromRequest(req, 'AuthSignature'); return { authStatus, From 9993f00fa9d77923dfd9fc461c3596c852a8f9ef Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 13 Jun 2025 06:11:56 -0700 Subject: [PATCH 3/8] fix tests and add changeset --- .changeset/open-swans-count.md | 6 ++++++ packages/express/src/__tests__/getAuth.test.ts | 2 +- packages/fastify/src/__tests__/getAuth.test.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/open-swans-count.md diff --git a/.changeset/open-swans-count.md b/.changeset/open-swans-count.md new file mode 100644 index 00000000000..17fa978acfa --- /dev/null +++ b/.changeset/open-swans-count.md @@ -0,0 +1,6 @@ +--- +'@clerk/backend': patch +'@clerk/nextjs': patch +--- + +Respect `acceptsToken` when returning unauthenticated session or machine object. diff --git a/packages/express/src/__tests__/getAuth.test.ts b/packages/express/src/__tests__/getAuth.test.ts index 7ce9acda9ee..382d5648312 100644 --- a/packages/express/src/__tests__/getAuth.test.ts +++ b/packages/express/src/__tests__/getAuth.test.ts @@ -40,7 +40,7 @@ describe('getAuth', () => { it('returns an unauthenticated auth object when the tokenType does not match acceptsToken', () => { const req = mockRequestWithAuth({ tokenType: 'session_token', userId: 'user_12345' }); const result = getAuth(req, { acceptsToken: 'api_key' }); - expect(result.tokenType).toBe('session_token'); // reflects the actual token found + expect(result.tokenType).toBe('api_key'); // reflects the actual token found // Properties specific to authenticated objects should be null or undefined expect(result.userId).toBeNull(); expect(result.orgId).toBeNull(); diff --git a/packages/fastify/src/__tests__/getAuth.test.ts b/packages/fastify/src/__tests__/getAuth.test.ts index 0c9f94a56dd..124ca033219 100644 --- a/packages/fastify/src/__tests__/getAuth.test.ts +++ b/packages/fastify/src/__tests__/getAuth.test.ts @@ -37,7 +37,7 @@ describe('getAuth(req)', () => { it('returns an unauthenticated auth object when the tokenType does not match acceptsToken', () => { const req = { auth: { tokenType: 'session_token', userId: 'user_12345' } } as unknown as FastifyRequest; const result = getAuth(req, { acceptsToken: 'api_key' }); - expect(result.tokenType).toBe('session_token'); // reflects the actual token found + expect(result.tokenType).toBe('api_key'); // reflects the actual token found expect(result.userId).toBeNull(); expect(result.orgId).toBeNull(); }); From cdf5ec8c87af0fe3e6282c9a4ec91b42a9265d2c Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 13 Jun 2025 11:11:14 -0700 Subject: [PATCH 4/8] chore: add isAuthenticated property to auth object --- .changeset/open-swans-count.md | 4 ++-- packages/backend/src/tokens/authObjects.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.changeset/open-swans-count.md b/.changeset/open-swans-count.md index 17fa978acfa..bc87ae0de0f 100644 --- a/.changeset/open-swans-count.md +++ b/.changeset/open-swans-count.md @@ -1,6 +1,6 @@ --- -'@clerk/backend': patch -'@clerk/nextjs': patch +'@clerk/backend': minor +'@clerk/nextjs': minor --- Respect `acceptsToken` when returning unauthenticated session or machine object. diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts index 4596c2578bd..c3be389bc66 100644 --- a/packages/backend/src/tokens/authObjects.ts +++ b/packages/backend/src/tokens/authObjects.ts @@ -58,6 +58,7 @@ export type SignedInAuthObject = SharedSignedInAuthObjectProperties & { * Used to help debug issues when using Clerk in development. */ debug: AuthObjectDebug; + isAuthenticated: true; }; /** @@ -78,6 +79,7 @@ export type SignedOutAuthObject = { getToken: ServerGetToken; has: CheckAuthorizationFromSessionClaims; debug: AuthObjectDebug; + isAuthenticated: false; }; /** @@ -121,6 +123,7 @@ export type AuthenticatedMachineObject[T] : never; @@ -141,6 +144,7 @@ export type UnauthenticatedMachineObject[T] : never; @@ -201,6 +205,7 @@ export function signedInAuthObject( plans: (sessionClaims.pla as string) || '', }), debug: createDebug({ ...authenticateContext, sessionToken }), + isAuthenticated: true, }; } @@ -226,6 +231,7 @@ export function signedOutAuthObject( getToken: () => Promise.resolve(null), has: () => false, debug: createDebug(debugData), + isAuthenticated: false, }; } @@ -244,6 +250,7 @@ export function authenticatedMachineObject( getToken: () => Promise.resolve(token), has: () => false, debug: createDebug(debugData), + isAuthenticated: true, }; // Type assertions are safe here since we know the verification result type matches the tokenType. @@ -303,6 +310,7 @@ export function unauthenticatedMachineObject( has: () => false, getToken: () => Promise.resolve(null), debug: createDebug(debugData), + isAuthenticated: false, }; switch (tokenType) { From a864c05a1ebe45666648bdc1034b8b96e78b35c7 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 13 Jun 2025 13:02:07 -0700 Subject: [PATCH 5/8] chore: add invalid token auth object --- .../backend/src/__tests__/exports.test.ts | 1 + packages/backend/src/index.ts | 2 +- packages/backend/src/internal.ts | 1 + .../src/tokens/__tests__/authObjects.test.ts | 18 +++++++---- .../src/tokens/__tests__/getAuth.test-d.ts | 6 ++-- packages/backend/src/tokens/authObjects.ts | 30 +++++++++++++++---- packages/backend/src/tokens/machine.ts | 8 +++-- packages/backend/src/tokens/types.ts | 4 ++- packages/nextjs/src/app-router/server/auth.ts | 13 ++++---- packages/nextjs/src/server/protect.ts | 4 +++ 10 files changed, 63 insertions(+), 24 deletions(-) diff --git a/packages/backend/src/__tests__/exports.test.ts b/packages/backend/src/__tests__/exports.test.ts index d597203dd7c..c9709c61722 100644 --- a/packages/backend/src/__tests__/exports.test.ts +++ b/packages/backend/src/__tests__/exports.test.ts @@ -50,6 +50,7 @@ describe('subpath /internal exports', () => { "getAuthObjectForAcceptedToken", "getAuthObjectFromJwt", "getMachineTokenType", + "invalidTokenAuthObject", "isMachineTokenByPrefix", "isMachineTokenType", "isTokenTypeAccepted", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 2b33d227388..78ec3aab09a 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -161,5 +161,5 @@ export type { /** * Auth objects */ -export type { AuthObject } from './tokens/authObjects'; +export type { AuthObject, InvalidTokenAuthObject } from './tokens/authObjects'; export type { SessionAuthObject, MachineAuthObject } from './tokens/types'; diff --git a/packages/backend/src/internal.ts b/packages/backend/src/internal.ts index 02a86836949..614bd13443e 100644 --- a/packages/backend/src/internal.ts +++ b/packages/backend/src/internal.ts @@ -31,6 +31,7 @@ export { signedInAuthObject, authenticatedMachineObject, unauthenticatedMachineObject, + invalidTokenAuthObject, getAuthObjectFromJwt, getAuthObjectForAcceptedToken, } from './tokens/authObjects'; diff --git a/packages/backend/src/tokens/__tests__/authObjects.test.ts b/packages/backend/src/tokens/__tests__/authObjects.test.ts index 758fa01f235..b521e82a8b4 100644 --- a/packages/backend/src/tokens/__tests__/authObjects.test.ts +++ b/packages/backend/src/tokens/__tests__/authObjects.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest'; import { mockTokens, mockVerificationResults } from '../../fixtures/machine'; import type { AuthenticateContext } from '../authenticateContext'; -import type { SignedOutAuthObject, UnauthenticatedMachineObject } from '../authObjects'; +import type { InvalidTokenAuthObject, UnauthenticatedMachineObject } from '../authObjects'; import { authenticatedMachineObject, getAuthObjectForAcceptedToken, @@ -405,19 +405,25 @@ describe('getAuthObjectForAcceptedToken', () => { expect(result).toBe(machineAuth); }); - it('returns unauthenticated machine object for parsed type if acceptsToken is array and token type does not match', () => { + it('returns InvalidTokenAuthObject if acceptsToken is array and token type does not match', () => { const result = getAuthObjectForAcceptedToken({ authObject: machineAuth, acceptsToken: ['machine_token', 'oauth_token'], }); - expect((result as UnauthenticatedMachineObject<'api_key'>).tokenType).toBe('api_key'); - expect((result as UnauthenticatedMachineObject<'api_key'>).id).toBeNull(); + expect((result as InvalidTokenAuthObject).tokenType).toBeNull(); + expect((result as InvalidTokenAuthObject).isAuthenticated).toBe(false); + }); + + it('returns InvalidTokenAuthObject if parsed type is not a machine token and does not match any in acceptsToken array', () => { + const result = getAuthObjectForAcceptedToken({ authObject: sessionAuth, acceptsToken: ['api_key', 'oauth_token'] }); + expect((result as InvalidTokenAuthObject).tokenType).toBeNull(); + expect((result as InvalidTokenAuthObject).isAuthenticated).toBe(false); }); it('returns signed-out session object if parsed type is not a machine token and does not match', () => { const result = getAuthObjectForAcceptedToken({ authObject: sessionAuth, acceptsToken: ['api_key', 'oauth_token'] }); - expect((result as SignedOutAuthObject).tokenType).toBe('session_token'); - expect((result as SignedOutAuthObject).userId).toBeNull(); + expect((result as InvalidTokenAuthObject).tokenType).toBeNull(); + expect((result as InvalidTokenAuthObject).isAuthenticated).toBe(false); }); it('returns unauthenticated object for requested type if acceptsToken is a single value and does not match', () => { diff --git a/packages/backend/src/tokens/__tests__/getAuth.test-d.ts b/packages/backend/src/tokens/__tests__/getAuth.test-d.ts index b0417b9b094..13e1a8873e7 100644 --- a/packages/backend/src/tokens/__tests__/getAuth.test-d.ts +++ b/packages/backend/src/tokens/__tests__/getAuth.test-d.ts @@ -1,6 +1,6 @@ import { expectTypeOf, test } from 'vitest'; -import type { AuthObject } from '../authObjects'; +import type { AuthObject, InvalidTokenAuthObject } from '../authObjects'; import type { GetAuthFn, MachineAuthObject, SessionAuthObject } from '../types'; // Across our SDKs, we have a getAuth() function @@ -22,10 +22,10 @@ test('infers the correct AuthObject type for each accepted token type', () => { // Array of token types expectTypeOf(getAuth(request, { acceptsToken: ['session_token', 'machine_token'] })).toMatchTypeOf< - SessionAuthObject | MachineAuthObject<'machine_token'> + SessionAuthObject | MachineAuthObject<'machine_token'> | InvalidTokenAuthObject >(); expectTypeOf(getAuth(request, { acceptsToken: ['machine_token', 'oauth_token'] })).toMatchTypeOf< - MachineAuthObject<'machine_token' | 'oauth_token'> + MachineAuthObject<'machine_token' | 'oauth_token'> | InvalidTokenAuthObject >(); // Any token type diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts index c3be389bc66..cadd4166476 100644 --- a/packages/backend/src/tokens/authObjects.ts +++ b/packages/backend/src/tokens/authObjects.ts @@ -148,6 +148,14 @@ export type UnauthenticatedMachineObject[T] : never; +export type InvalidTokenAuthObject = { + isAuthenticated: false; + tokenType: null; + getToken: () => Promise; + has: () => false; + debug: AuthObjectDebug; +}; + /** * @interface */ @@ -155,7 +163,8 @@ export type AuthObject = | SignedInAuthObject | SignedOutAuthObject | AuthenticatedMachineObject - | UnauthenticatedMachineObject; + | UnauthenticatedMachineObject + | InvalidTokenAuthObject; const createDebug = (data: AuthObjectDebugData | undefined) => { return () => { @@ -349,6 +358,19 @@ export function unauthenticatedMachineObject( } } +/** + * @internal + */ +export function invalidTokenAuthObject(): InvalidTokenAuthObject { + return { + isAuthenticated: false, + tokenType: null, + getToken: () => Promise.resolve(null), + has: () => false, + debug: () => ({}), + }; +} + /** * Auth objects moving through the server -> client boundary need to be serializable * as we need to ensure that they can be transferred via the network as pure strings. @@ -424,10 +446,8 @@ export function getAuthObjectForAcceptedToken({ if (Array.isArray(acceptsToken)) { if (!isTokenTypeAccepted(authObject.tokenType, acceptsToken)) { - if (isMachineTokenType(authObject.tokenType)) { - return unauthenticatedMachineObject(authObject.tokenType, authObject.debug); - } - return signedOutAuthObject(authObject.debug); + // If the token is not in the accepted array, return invalidTokenAuthObject + return invalidTokenAuthObject(); } return authObject; } diff --git a/packages/backend/src/tokens/machine.ts b/packages/backend/src/tokens/machine.ts index 6be97d75103..17df764a429 100644 --- a/packages/backend/src/tokens/machine.ts +++ b/packages/backend/src/tokens/machine.ts @@ -52,14 +52,18 @@ export function getMachineTokenType(token: string): MachineTokenType { /** * Check if a token type is accepted given a requested token type or list of token types. * - * @param tokenType - The token type to check + * @param tokenType - The token type to check (can be null if the token is invalid) * @param acceptsToken - The requested token type or list of token types * @returns true if the token type is accepted */ export const isTokenTypeAccepted = ( - tokenType: TokenType, + tokenType: TokenType | null, acceptsToken: NonNullable, ): boolean => { + if (!tokenType) { + return false; + } + if (acceptsToken === 'any') { return true; } diff --git a/packages/backend/src/tokens/types.ts b/packages/backend/src/tokens/types.ts index badda7d18f3..2b95dfb6c23 100644 --- a/packages/backend/src/tokens/types.ts +++ b/packages/backend/src/tokens/types.ts @@ -5,6 +5,7 @@ import type { ApiClient, APIKey, IdPOAuthAccessToken, MachineToken } from '../ap import type { AuthenticatedMachineObject, AuthObject, + InvalidTokenAuthObject, SignedInAuthObject, SignedOutAuthObject, UnauthenticatedMachineObject, @@ -200,7 +201,8 @@ export interface GetAuthFn req: RequestType, options: AuthOptions & { acceptsToken: T }, ): MaybePromise< - InferAuthObjectFromTokenArray>>, + | InferAuthObjectFromTokenArray>> + | InvalidTokenAuthObject, ReturnsPromise >; diff --git a/packages/nextjs/src/app-router/server/auth.ts b/packages/nextjs/src/app-router/server/auth.ts index ec5b151f7ee..cd5ce02c81d 100644 --- a/packages/nextjs/src/app-router/server/auth.ts +++ b/packages/nextjs/src/app-router/server/auth.ts @@ -1,4 +1,4 @@ -import type { AuthObject, MachineAuthObject, SessionAuthObject } from '@clerk/backend'; +import type { AuthObject, InvalidTokenAuthObject, MachineAuthObject, SessionAuthObject } from '@clerk/backend'; import type { AuthenticateRequestOptions, InferAuthObjectFromToken, @@ -56,11 +56,12 @@ export interface AuthFn> { ( options: AuthOptions & { acceptsToken: T }, ): Promise< - InferAuthObjectFromTokenArray< - T, - SessionAuthWithRedirect, - MachineAuthObject> - > + | InferAuthObjectFromTokenArray< + T, + SessionAuthWithRedirect, + MachineAuthObject> + > + | InvalidTokenAuthObject >; /** diff --git a/packages/nextjs/src/server/protect.ts b/packages/nextjs/src/server/protect.ts index cfb0dea5628..f74918893a8 100644 --- a/packages/nextjs/src/server/protect.ts +++ b/packages/nextjs/src/server/protect.ts @@ -145,6 +145,10 @@ export function createProtect(opts: { return handleUnauthorized(); } + if (authObject.tokenType === null) { + return handleUnauthorized(); + } + if (authObject.tokenType !== TokenType.SessionToken) { // For machine tokens, we only check if they're authenticated // They don't have session status or organization permissions From fc025c0084ae19e64eb5f709f6b004f4581768f1 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 13 Jun 2025 13:10:59 -0700 Subject: [PATCH 6/8] chore: adjustments --- packages/backend/src/tokens/authObjects.ts | 2 +- .../server/__tests__/getAuthDataFromRequest.test.ts | 6 +++--- .../nextjs/src/server/data/getAuthDataFromRequest.ts | 11 ++++++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts index cadd4166476..a1ec6892b0e 100644 --- a/packages/backend/src/tokens/authObjects.ts +++ b/packages/backend/src/tokens/authObjects.ts @@ -446,7 +446,7 @@ export function getAuthObjectForAcceptedToken({ if (Array.isArray(acceptsToken)) { if (!isTokenTypeAccepted(authObject.tokenType, acceptsToken)) { - // If the token is not in the accepted array, return invalidTokenAuthObject + // If the token is not in the accepted array, return invalid token auth object return invalidTokenAuthObject(); } return authObject; diff --git a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts index 09e80594c75..3f17613ba50 100644 --- a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts +++ b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts @@ -46,7 +46,7 @@ describe('getAuthDataFromRequestAsync', () => { expect((auth as AuthenticatedMachineObject<'machine_token'>).machineId).toBeNull(); }); - it('returns unauthenticated machine object for the actual parsed machine token type when token type does not match any in acceptsToken array', async () => { + it('returns invalid token auth object when token type does not match any in acceptsToken array', async () => { const req = mockRequest({ url: '/api/protected', headers: new Headers({ @@ -58,8 +58,8 @@ describe('getAuthDataFromRequestAsync', () => { acceptsToken: ['machine_token', 'oauth_token', 'session_token'], }); - expect(auth.tokenType).toBe('api_key'); - expect((auth as AuthenticatedMachineObject<'api_key'>).userId).toBeNull(); + expect(auth.tokenType).toBeNull(); + expect(auth.isAuthenticated).toBe(false); }); it('returns authenticated api_key object when array contains only api_key and token is ak_xxx and verification passes', async () => { diff --git a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts index 771498245ee..7379a03a7cd 100644 --- a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts +++ b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts @@ -6,6 +6,7 @@ import { constants, getAuthObjectFromJwt, getMachineTokenType, + invalidTokenAuthObject, isMachineTokenByPrefix, isMachineTokenType, isTokenTypeAccepted, @@ -168,9 +169,13 @@ async function handleMachineToken({ acceptsToken: AuthenticateRequestOptions['acceptsToken']; options: Record; }) { - if (!tokenType) { - return signedOutAuthObject(options); + if (Array.isArray(acceptsToken)) { + // If the token is not in the accepted array, return invalid token auth object + if (!isTokenTypeAccepted(tokenType, acceptsToken)) { + return invalidTokenAuthObject(); + } } + if (!isTokenTypeAccepted(tokenType, acceptsToken ?? TokenType.SessionToken)) { return unauthenticatedMachineObject(tokenType, options); } @@ -206,7 +211,7 @@ async function handleIntentBased({ } if (!isTokenTypeAccepted(tokenType, acceptsToken ?? TokenType.SessionToken)) { if (intendedType && isMachineTokenType(intendedType)) { - return unauthenticatedMachineObject(intendedType as MachineTokenType, options); + return unauthenticatedMachineObject(intendedType, options); } return signedOutAuthObject(options); } From d01f40d8bcd9e6400b730c7f36a5e0a4e450228c Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 13 Jun 2025 13:28:16 -0700 Subject: [PATCH 7/8] chore: adjustments --- .../src/server/data/getAuthDataFromRequest.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts index 7379a03a7cd..6e7bfb40814 100644 --- a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts +++ b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts @@ -109,12 +109,12 @@ export const getAuthDataFromRequestAsync = async ( if (Array.isArray(acceptsToken)) { if (isMachine) { - if (!tokenType) { - return signedOutAuthObject(options); - } - return handleMachineToken({ bearerToken, tokenType, acceptsToken, options }); - } else { - return signedOutAuthObject(options); + return handleMachineToken({ + bearerToken, + tokenType: tokenType as MachineTokenType, + acceptsToken, + options, + }); } } else { let intendedType: TokenType | undefined; @@ -135,6 +135,13 @@ export const getAuthDataFromRequestAsync = async ( } } + if (Array.isArray(acceptsToken)) { + if (!isTokenTypeAccepted(TokenType.SessionToken, acceptsToken)) { + return invalidTokenAuthObject(); + } + } + + // Fall through to session logic return getAuthDataFromRequestSync(req, opts); }; From e672cd3ac0869aedd4534b58a1cfde71f2111b83 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 13 Jun 2025 13:33:38 -0700 Subject: [PATCH 8/8] fix test types --- packages/express/src/__tests__/getAuth.test.ts | 7 +++++-- packages/fastify/src/__tests__/getAuth.test.ts | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/express/src/__tests__/getAuth.test.ts b/packages/express/src/__tests__/getAuth.test.ts index 382d5648312..750994777a4 100644 --- a/packages/express/src/__tests__/getAuth.test.ts +++ b/packages/express/src/__tests__/getAuth.test.ts @@ -1,3 +1,5 @@ +import type { AuthenticatedMachineObject } from '@clerk/backend/internal'; + import { getAuth } from '../getAuth'; import { mockRequest, mockRequestWithAuth } from './helpers'; @@ -33,8 +35,9 @@ describe('getAuth', () => { const req = mockRequestWithAuth({ tokenType: 'machine_token', id: 'm2m_1234' }); const result = getAuth(req, { acceptsToken: ['machine_token', 'api_key'] }); expect(result.tokenType).toBe('machine_token'); - expect(result.id).toBe('m2m_1234'); - expect(result.subject).toBeUndefined(); + + expect((result as AuthenticatedMachineObject<'machine_token'>).id).toBe('m2m_1234'); + expect((result as AuthenticatedMachineObject<'machine_token'>).subject).toBeUndefined(); }); it('returns an unauthenticated auth object when the tokenType does not match acceptsToken', () => { diff --git a/packages/fastify/src/__tests__/getAuth.test.ts b/packages/fastify/src/__tests__/getAuth.test.ts index 124ca033219..9d5dfe8f660 100644 --- a/packages/fastify/src/__tests__/getAuth.test.ts +++ b/packages/fastify/src/__tests__/getAuth.test.ts @@ -1,3 +1,4 @@ +import type { AuthenticatedMachineObject } from '@clerk/backend/internal'; import type { FastifyRequest } from 'fastify'; import { getAuth } from '../getAuth'; @@ -30,8 +31,8 @@ describe('getAuth(req)', () => { const req = { auth: { tokenType: 'machine_token', id: 'm2m_1234' } } as unknown as FastifyRequest; const result = getAuth(req, { acceptsToken: ['machine_token', 'api_key'] }); expect(result.tokenType).toBe('machine_token'); - expect(result.id).toBe('m2m_1234'); - expect(result.subject).toBeUndefined(); + expect((result as AuthenticatedMachineObject<'machine_token'>).id).toBe('m2m_1234'); + expect((result as AuthenticatedMachineObject<'machine_token'>).subject).toBeUndefined(); }); it('returns an unauthenticated auth object when the tokenType does not match acceptsToken', () => {