From e003a8e623b579ce3f7c11dae9b50e0ee850f0b6 Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sat, 14 Jun 2025 10:33:59 -0700
Subject: [PATCH 01/13] chore: clean up and improve token handling

---
 .../server/__tests__/clerkMiddleware.test.ts  |  16 +--
 .../__tests__/getAuthDataFromRequest.test.ts  |  31 ++++
 .../src/server/data/getAuthDataFromRequest.ts | 135 +++---------------
 packages/nextjs/src/server/protect.ts         |   6 +-
 4 files changed, 60 insertions(+), 128 deletions(-)

diff --git a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
index 238a77e7cdc..9912dbed6f1 100644
--- a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
+++ b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts
@@ -477,7 +477,7 @@ describe('clerkMiddleware(params)', () => {
       const req = mockRequest({
         url: '/api/protected',
         headers: new Headers({
-          [constants.Headers.Authorization]: 'Bearer api_key_xxxxxxxxxxxxxxxxxx',
+          [constants.Headers.Authorization]: 'Bearer ak_123',
         }),
       });
 
@@ -485,7 +485,7 @@ describe('clerkMiddleware(params)', () => {
         publishableKey,
         status: AuthStatus.SignedIn,
         headers: new Headers(),
-        toAuth: () => ({ tokenType: TokenType.ApiKey, id: 'api_key_xxxxxxxxxxxxxxxxxx' }),
+        toAuth: () => ({ tokenType: TokenType.ApiKey, id: 'ak_123', isAuthenticated: true }),
       });
 
       const resp = await clerkMiddleware(async auth => {
@@ -525,7 +525,7 @@ describe('clerkMiddleware(params)', () => {
       const req = mockRequest({
         url: '/protected',
         headers: new Headers({
-          [constants.Headers.Authorization]: 'Bearer api_key_xxxxxxxxxxxxxxxxxx',
+          [constants.Headers.Authorization]: 'Bearer ak_123',
         }),
       });
 
@@ -552,7 +552,7 @@ describe('clerkMiddleware(params)', () => {
       const req = mockRequest({
         url: '/protected',
         headers: new Headers({
-          [constants.Headers.Authorization]: 'Bearer oauth_token_xxxxxxxxxxxxxxxxxx',
+          [constants.Headers.Authorization]: 'Bearer oat_123',
         }),
       });
 
@@ -658,7 +658,7 @@ describe('clerkMiddleware(params)', () => {
       const req = mockRequest({
         url: '/api/protected',
         headers: new Headers({
-          [constants.Headers.Authorization]: 'Bearer m2m_xxxxxxxxxxxxxxxxxx',
+          [constants.Headers.Authorization]: 'Bearer m2m_123',
         }),
       });
 
@@ -681,7 +681,7 @@ describe('clerkMiddleware(params)', () => {
       const req = mockRequest({
         url: '/api/protected',
         headers: new Headers({
-          [constants.Headers.Authorization]: 'Bearer api_key_xxx',
+          [constants.Headers.Authorization]: 'Bearer ak_123',
         }),
       });
 
@@ -689,7 +689,7 @@ describe('clerkMiddleware(params)', () => {
         publishableKey,
         status: AuthStatus.SignedIn,
         headers: new Headers(),
-        toAuth: () => ({ tokenType: TokenType.ApiKey, id: 'api_key_xxxxxxxxxxxxxxxxxx' }),
+        toAuth: () => ({ tokenType: TokenType.ApiKey, id: 'ak_123', isAuthenticated: true }),
       });
 
       const resp = await clerkMiddleware(async auth => {
@@ -705,7 +705,7 @@ describe('clerkMiddleware(params)', () => {
       const req = mockRequest({
         url: '/api/protected',
         headers: new Headers({
-          [constants.Headers.Authorization]: 'Bearer api_key_xxx',
+          [constants.Headers.Authorization]: 'Bearer ak_123',
         }),
       });
 
diff --git a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
index 3f17613ba50..4de8532f6c3 100644
--- a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
+++ b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
@@ -31,6 +31,19 @@ const mockRequest = (params: MockRequestParams) => {
 
 describe('getAuthDataFromRequestAsync', () => {
   it('returns unauthenticated machine object when token type does not match', async () => {
+    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+      data: undefined,
+      tokenType: 'machine_token',
+      errors: [
+        {
+          message: 'Token type mismatch',
+          code: 'token-invalid',
+          status: 401,
+          name: 'MachineTokenVerificationError',
+          getFullMessage: () => 'Token type mismatch',
+        },
+      ],
+    });
     const req = mockRequest({
       url: '/api/protected',
       headers: new Headers({
@@ -44,9 +57,23 @@ describe('getAuthDataFromRequestAsync', () => {
 
     expect(auth.tokenType).toBe('machine_token');
     expect((auth as AuthenticatedMachineObject<'machine_token'>).machineId).toBeNull();
+    expect(auth.isAuthenticated).toBe(false);
   });
 
   it('returns invalid token auth object when token type does not match any in acceptsToken array', async () => {
+    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+      data: undefined,
+      tokenType: 'machine_token',
+      errors: [
+        {
+          message: 'Token type mismatch',
+          code: 'token-invalid',
+          status: 401,
+          name: 'MachineTokenVerificationError',
+          getFullMessage: () => 'Token type mismatch',
+        },
+      ],
+    });
     const req = mockRequest({
       url: '/api/protected',
       headers: new Headers({
@@ -82,6 +109,7 @@ describe('getAuthDataFromRequestAsync', () => {
 
     expect(auth.tokenType).toBe('api_key');
     expect((auth as AuthenticatedMachineObject<'api_key'>).id).toBe('ak_123');
+    expect(auth.isAuthenticated).toBe(true);
   });
 
   it('returns authenticated machine object when token type matches', async () => {
@@ -104,6 +132,7 @@ describe('getAuthDataFromRequestAsync', () => {
 
     expect(auth.tokenType).toBe('api_key');
     expect((auth as AuthenticatedMachineObject<'api_key'>).id).toBe('ak_123');
+    expect(auth.isAuthenticated).toBe(true);
   });
 
   it('falls back to session token handling', async () => {
@@ -117,6 +146,7 @@ describe('getAuthDataFromRequestAsync', () => {
     const auth = await getAuthDataFromRequestAsync(req);
     expect(auth.tokenType).toBe('session_token');
     expect((auth as SignedOutAuthObject).userId).toBeNull();
+    expect(auth.isAuthenticated).toBe(false);
   });
 });
 
@@ -135,5 +165,6 @@ describe('getAuthDataFromRequestSync', () => {
 
     expect(auth.tokenType).toBe('session_token');
     expect(auth.userId).toBeNull();
+    expect(auth.isAuthenticated).toBe(false);
   });
 });
diff --git a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
index 6e7bfb40814..81bcc810047 100644
--- a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
+++ b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
@@ -4,13 +4,12 @@ import {
   type AuthenticateRequestOptions,
   AuthStatus,
   constants,
+  getAuthObjectForAcceptedToken,
   getAuthObjectFromJwt,
   getMachineTokenType,
   invalidTokenAuthObject,
   isMachineTokenByPrefix,
-  isMachineTokenType,
   isTokenTypeAccepted,
-  type MachineTokenType,
   type SignedInAuthObject,
   type SignedOutAuthObject,
   signedOutAuthObject,
@@ -33,6 +32,10 @@ export type GetAuthDataFromRequestOptions = {
   acceptsToken?: AuthenticateRequestOptions['acceptsToken'];
 } & PendingSessionOptions;
 
+/**
+ * Given a request object, builds an auth object from the request data. Used in server-side environments to get access
+ * to auth data for a given request.
+ */
 export const getAuthDataFromRequestSync = (
   req: RequestLike,
   { treatPendingAsSignedOut = true, ...opts }: GetAuthDataFromRequestOptions = {},
@@ -78,10 +81,6 @@ export const getAuthDataFromRequestSync = (
 };
 
 /**
- * Note: We intentionally avoid using interface/function overloads here since these functions
- * are used internally. The complex type overloads are more valuable at the public API level
- * (like in auth.protect(), auth()) where users interact directly with the types.
- *
  * Given a request object, builds an auth object from the request data. Used in server-side environments to get access
  * to auth data for a given request.
  */
@@ -103,46 +102,25 @@ export const getAuthDataFromRequestAsync = async (
     authReason,
   };
 
-  if (bearerToken) {
-    const isMachine = isMachineTokenByPrefix(bearerToken);
-    const tokenType = isMachine ? getMachineTokenType(bearerToken) : undefined;
-
-    if (Array.isArray(acceptsToken)) {
-      if (isMachine) {
-        return handleMachineToken({
-          bearerToken,
-          tokenType: tokenType as MachineTokenType,
-          acceptsToken,
-          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;
-      }
-    }
+  const hasMachineToken = bearerToken && isMachineTokenByPrefix(bearerToken);
+  if (hasMachineToken) {
+    const machineTokenType = getMachineTokenType(bearerToken);
+    const { data, errors } = await verifyMachineAuthToken(bearerToken, options);
+    const authObject = errors
+      ? unauthenticatedMachineObject(machineTokenType, options)
+      : authenticatedMachineObject(machineTokenType, bearerToken, data);
+    return getAuthObjectForAcceptedToken({ authObject, acceptsToken });
   }
 
-  if (Array.isArray(acceptsToken)) {
-    if (!isTokenTypeAccepted(TokenType.SessionToken, acceptsToken)) {
-      return invalidTokenAuthObject();
-    }
+  // If a random token is present and acceptsToken is an array that does NOT include session_token,
+  // return invalidTokenAuthObject.
+  if (bearerToken && Array.isArray(acceptsToken) && !acceptsToken.includes(TokenType.SessionToken)) {
+    return invalidTokenAuthObject();
   }
 
-  // Fall through to session logic
-  return getAuthDataFromRequestSync(req, opts);
+  // Fallback to session logic (sync version) for all other cases
+  const authObject = getAuthDataFromRequestSync(req, opts);
+  return getAuthObjectForAcceptedToken({ authObject, acceptsToken });
 };
 
 const getAuthHeaders = (req: RequestLike) => {
@@ -160,76 +138,3 @@ 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<string, any>;
-}) {
-  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);
-  }
-  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<string, any>;
-}) {
-  if (isMachine) {
-    if (!tokenType) {
-      return signedOutAuthObject(options);
-    }
-    if (!isTokenTypeAccepted(tokenType, acceptsToken ?? TokenType.SessionToken)) {
-      if (intendedType && isMachineTokenType(intendedType)) {
-        return unauthenticatedMachineObject(intendedType, 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;
-}
diff --git a/packages/nextjs/src/server/protect.ts b/packages/nextjs/src/server/protect.ts
index f74918893a8..b5372c766a5 100644
--- a/packages/nextjs/src/server/protect.ts
+++ b/packages/nextjs/src/server/protect.ts
@@ -145,14 +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
-      if (!authObject.id) {
+      if (!authObject.isAuthenticated) {
         return handleUnauthorized();
       }
       return authObject;

From 4b1d9aef07546f260ea38b45849f309a155df65c Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sat, 14 Jun 2025 11:32:35 -0700
Subject: [PATCH 02/13] chore: add more tests

---
 packages/backend/src/tokens/authObjects.ts    |   9 +-
 .../__tests__/getAuthDataFromRequest.test.ts  | 109 ++++++++++++++++--
 2 files changed, 105 insertions(+), 13 deletions(-)

diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts
index a1ec6892b0e..0eee63677b1 100644
--- a/packages/backend/src/tokens/authObjects.ts
+++ b/packages/backend/src/tokens/authObjects.ts
@@ -428,7 +428,8 @@ export const getAuthObjectFromJwt = (
  * Returns an auth object matching the requested token type(s).
  *
  * 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
+ *   - an invalid token auth object if the token is not in the accepted array
+ *   - an unauthenticated machine object for machine tokens, or
  *   - a signed-out session object otherwise.
  *
  * This ensures the returned object always matches the developer's intent.
@@ -440,19 +441,20 @@ export function getAuthObjectForAcceptedToken({
   authObject: AuthObject;
   acceptsToken: AuthenticateRequestOptions['acceptsToken'];
 }): AuthObject {
+  // 1. any token: return as-is
   if (acceptsToken === 'any') {
     return authObject;
   }
 
+  // 2. array of tokens: must match one of the accepted types
   if (Array.isArray(acceptsToken)) {
     if (!isTokenTypeAccepted(authObject.tokenType, acceptsToken)) {
-      // If the token is not in the accepted array, return invalid token auth object
       return invalidTokenAuthObject();
     }
     return authObject;
   }
 
-  // Single value: Intent based
+  // 3. single token: must match exactly, else return appropriate unauthenticated object
   if (!isTokenTypeAccepted(authObject.tokenType, acceptsToken)) {
     if (isMachineTokenType(acceptsToken)) {
       return unauthenticatedMachineObject(acceptsToken, authObject.debug);
@@ -460,5 +462,6 @@ export function getAuthObjectForAcceptedToken({
     return signedOutAuthObject(authObject.debug);
   }
 
+  // 4. default: return as-is
   return authObject;
 }
diff --git a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
index 4de8532f6c3..25c1d858490 100644
--- a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
+++ b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
@@ -29,6 +29,16 @@ const mockRequest = (params: MockRequestParams) => {
   return new NextRequest(new URL(url, 'https://www.clerk.com').toString(), { method, headers: headersWithCookie });
 };
 
+const machineTokenErrorMock = [
+  {
+    message: 'Token type mismatch',
+    code: 'token-invalid',
+    status: 401,
+    name: 'MachineTokenVerificationError',
+    getFullMessage: () => 'Token type mismatch',
+  },
+];
+
 describe('getAuthDataFromRequestAsync', () => {
   it('returns unauthenticated machine object when token type does not match', async () => {
     vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
@@ -89,7 +99,7 @@ describe('getAuthDataFromRequestAsync', () => {
     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 () => {
+  it('returns authenticated object when token type exists in acceptsToken array', async () => {
     vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
       data: { id: 'ak_123', subject: 'user_12345' } as any,
       tokenType: 'api_key',
@@ -99,12 +109,12 @@ describe('getAuthDataFromRequestAsync', () => {
     const req = mockRequest({
       url: '/api/protected',
       headers: new Headers({
-        [constants.Headers.Authorization]: 'Bearer ak_xxx',
+        [constants.Headers.Authorization]: 'Bearer ak_123',
       }),
     });
 
     const auth = await getAuthDataFromRequestAsync(req, {
-      acceptsToken: ['api_key'],
+      acceptsToken: ['api_key', 'machine_token'],
     });
 
     expect(auth.tokenType).toBe('api_key');
@@ -112,27 +122,106 @@ describe('getAuthDataFromRequestAsync', () => {
     expect(auth.isAuthenticated).toBe(true);
   });
 
-  it('returns authenticated machine object when token type matches', async () => {
+  it('returns authenticated api_key object when token is valid and acceptsToken is api_key', 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_123`,
+      }),
+    });
+    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'api_key' });
+    expect(auth.tokenType).toBe('api_key');
+    expect(auth.isAuthenticated).toBe(true);
+  });
 
+  it('returns authenticated oauth_token object when token is valid and acceptsToken is oauth_token', async () => {
+    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+      data: { id: 'oat_123', subject: 'user_12345' } as any,
+      tokenType: 'oauth_token',
+      errors: undefined,
+    });
     const req = mockRequest({
       url: '/api/protected',
       headers: new Headers({
-        [constants.Headers.Authorization]: 'Bearer ak_xxx',
+        [constants.Headers.Authorization]: `Bearer oat_123`,
       }),
     });
+    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'oauth_token' });
+    expect(auth.tokenType).toBe('oauth_token');
+    expect(auth.isAuthenticated).toBe(true);
+  });
 
-    const auth = await getAuthDataFromRequestAsync(req, {
-      acceptsToken: 'api_key',
+  it('returns authenticated machine_token object when token is valid and acceptsToken is machine_token', async () => {
+    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+      data: { id: 'm2m_123', subject: 'mch_123' } as any,
+      tokenType: 'machine_token',
+      errors: undefined,
+    });
+    const req = mockRequest({
+      url: '/api/protected',
+      headers: new Headers({
+        [constants.Headers.Authorization]: `Bearer mt_123`,
+      }),
     });
+    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'machine_token' });
+    expect(auth.tokenType).toBe('machine_token');
+    expect(auth.isAuthenticated).toBe(true);
+  });
 
+  it('returns unauthenticated api_key object when token is invalid', async () => {
+    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+      data: undefined,
+      tokenType: 'api_key',
+      errors: machineTokenErrorMock as any,
+    });
+    const req = mockRequest({
+      url: '/api/protected',
+      headers: new Headers({
+        [constants.Headers.Authorization]: `Bearer ak_123`,
+      }),
+    });
+    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'api_key' });
     expect(auth.tokenType).toBe('api_key');
-    expect((auth as AuthenticatedMachineObject<'api_key'>).id).toBe('ak_123');
-    expect(auth.isAuthenticated).toBe(true);
+    expect(auth.isAuthenticated).toBe(false);
+  });
+
+  it('returns unauthenticated oauth_token object when token is invalid', async () => {
+    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+      data: undefined,
+      tokenType: 'oauth_token',
+      errors: machineTokenErrorMock as any,
+    });
+    const req = mockRequest({
+      url: '/api/protected',
+      headers: new Headers({
+        [constants.Headers.Authorization]: `Bearer oat_123`,
+      }),
+    });
+    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'oauth_token' });
+    expect(auth.tokenType).toBe('oauth_token');
+    expect(auth.isAuthenticated).toBe(false);
+  });
+
+  it('returns unauthenticated machine_token object when token is invalid', async () => {
+    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+      data: undefined,
+      tokenType: 'machine_token',
+      errors: machineTokenErrorMock as any,
+    });
+    const req = mockRequest({
+      url: '/api/protected',
+      headers: new Headers({
+        [constants.Headers.Authorization]: `Bearer mt_123`,
+      }),
+    });
+    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'machine_token' });
+    expect(auth.tokenType).toBe('machine_token');
+    expect(auth.isAuthenticated).toBe(false);
   });
 
   it('falls back to session token handling', async () => {
@@ -155,7 +244,7 @@ describe('getAuthDataFromRequestSync', () => {
     const req = mockRequest({
       url: '/api/protected',
       headers: new Headers({
-        [constants.Headers.Authorization]: 'Bearer api_key_xxx',
+        [constants.Headers.Authorization]: 'Bearer ak_123',
       }),
     });
 

From 471c07f702ca872d545ab48e36eb863a16d4cdba Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sat, 14 Jun 2025 14:47:05 -0700
Subject: [PATCH 03/13] chore: add invalid token support in authenticateRequest

---
 .../src/tokens/__tests__/request.test-d.ts    |  2 +-
 .../src/tokens/__tests__/request.test.ts      | 10 +++-
 packages/backend/src/tokens/authObjects.ts    |  6 +--
 packages/backend/src/tokens/authStatus.ts     | 47 +++++++++++++++----
 packages/backend/src/tokens/request.ts        | 32 +++++++++++--
 5 files changed, 76 insertions(+), 21 deletions(-)

diff --git a/packages/backend/src/tokens/__tests__/request.test-d.ts b/packages/backend/src/tokens/__tests__/request.test-d.ts
index dcfc3f972a7..8825ed00c49 100644
--- a/packages/backend/src/tokens/__tests__/request.test-d.ts
+++ b/packages/backend/src/tokens/__tests__/request.test-d.ts
@@ -26,7 +26,7 @@ test('returns the correct `authenticateRequest()` return type for each accepted
   // Array of token types
   expectTypeOf(
     authenticateRequest(request, { acceptsToken: ['session_token', 'api_key', 'machine_token'] }),
-  ).toMatchTypeOf<Promise<RequestState<'session_token' | 'api_key' | 'machine_token'>>>();
+  ).toMatchTypeOf<Promise<RequestState<'session_token' | 'api_key' | 'machine_token' | null>>>();
 
   // Any token type
   expectTypeOf(authenticateRequest(request, { acceptsToken: 'any' })).toMatchTypeOf<Promise<RequestState<TokenType>>>();
diff --git a/packages/backend/src/tokens/__tests__/request.test.ts b/packages/backend/src/tokens/__tests__/request.test.ts
index 26b26fec511..48246de34ba 100644
--- a/packages/backend/src/tokens/__tests__/request.test.ts
+++ b/packages/backend/src/tokens/__tests__/request.test.ts
@@ -1240,6 +1240,7 @@ describe('tokens.authenticateRequest(options)', () => {
         });
         expect(requestState.toAuth()).toBeMachineUnauthenticatedToAuth({
           tokenType,
+          isAuthenticated: false,
         });
       });
     });
@@ -1289,6 +1290,7 @@ describe('tokens.authenticateRequest(options)', () => {
         });
         expect(result.toAuth()).toBeMachineUnauthenticatedToAuth({
           tokenType: 'api_key',
+          isAuthenticated: false,
         });
       });
 
@@ -1303,6 +1305,7 @@ describe('tokens.authenticateRequest(options)', () => {
         });
         expect(result.toAuth()).toBeMachineUnauthenticatedToAuth({
           tokenType: 'oauth_token',
+          isAuthenticated: false,
         });
       });
 
@@ -1317,6 +1320,7 @@ describe('tokens.authenticateRequest(options)', () => {
         });
         expect(result.toAuth()).toBeMachineUnauthenticatedToAuth({
           tokenType: 'machine_token',
+          isAuthenticated: false,
         });
       });
 
@@ -1331,6 +1335,7 @@ describe('tokens.authenticateRequest(options)', () => {
         });
         expect(result.toAuth()).toBeMachineUnauthenticatedToAuth({
           tokenType: 'machine_token',
+          isAuthenticated: false,
         });
       });
     });
@@ -1360,12 +1365,13 @@ describe('tokens.authenticateRequest(options)', () => {
         );
 
         expect(requestState).toBeMachineUnauthenticated({
-          tokenType: 'machine_token',
+          tokenType: null,
           reason: AuthErrorReason.TokenTypeMismatch,
           message: '',
         });
         expect(requestState.toAuth()).toBeMachineUnauthenticatedToAuth({
-          tokenType: 'machine_token',
+          tokenType: null,
+          isAuthenticated: false,
         });
       });
     });
diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts
index 0eee63677b1..9c5682c3e01 100644
--- a/packages/backend/src/tokens/authObjects.ts
+++ b/packages/backend/src/tokens/authObjects.ts
@@ -434,13 +434,13 @@ export const getAuthObjectFromJwt = (
  *
  * This ensures the returned object always matches the developer's intent.
  */
-export function getAuthObjectForAcceptedToken({
+export const getAuthObjectForAcceptedToken = ({
   authObject,
   acceptsToken = TokenType.SessionToken,
 }: {
   authObject: AuthObject;
   acceptsToken: AuthenticateRequestOptions['acceptsToken'];
-}): AuthObject {
+}): AuthObject => {
   // 1. any token: return as-is
   if (acceptsToken === 'any') {
     return authObject;
@@ -464,4 +464,4 @@ export function getAuthObjectForAcceptedToken({
 
   // 4. default: return as-is
   return authObject;
-}
+};
diff --git a/packages/backend/src/tokens/authStatus.ts b/packages/backend/src/tokens/authStatus.ts
index 0699933e81c..cefe6f468bb 100644
--- a/packages/backend/src/tokens/authStatus.ts
+++ b/packages/backend/src/tokens/authStatus.ts
@@ -5,12 +5,14 @@ import type { TokenVerificationErrorReason } from '../errors';
 import type { AuthenticateContext } from './authenticateContext';
 import type {
   AuthenticatedMachineObject,
+  InvalidTokenAuthObject,
   SignedInAuthObject,
   SignedOutAuthObject,
   UnauthenticatedMachineObject,
 } from './authObjects';
 import {
   authenticatedMachineObject,
+  invalidTokenAuthObject,
   signedInAuthObject,
   signedOutAuthObject,
   unauthenticatedMachineObject,
@@ -27,13 +29,15 @@ export const AuthStatus = {
 
 export type AuthStatus = (typeof AuthStatus)[keyof typeof AuthStatus];
 
-type ToAuth<T extends TokenType, Authenticated extends boolean> = T extends SessionTokenType
-  ? Authenticated extends true
-    ? (opts?: PendingSessionOptions) => SignedInAuthObject
-    : () => SignedOutAuthObject
-  : Authenticated extends true
-    ? () => AuthenticatedMachineObject<Exclude<T, SessionTokenType>>
-    : () => UnauthenticatedMachineObject<Exclude<T, SessionTokenType>>;
+type ToAuth<T extends TokenType | null, Authenticated extends boolean> = T extends null
+  ? () => InvalidTokenAuthObject
+  : T extends SessionTokenType
+    ? Authenticated extends true
+      ? (opts?: PendingSessionOptions) => SignedInAuthObject
+      : () => SignedOutAuthObject
+    : Authenticated extends true
+      ? () => AuthenticatedMachineObject<Exclude<T, SessionTokenType | null>>
+      : () => UnauthenticatedMachineObject<Exclude<T, SessionTokenType | null>>;
 
 export type AuthenticatedState<T extends TokenType = SessionTokenType> = {
   status: typeof AuthStatus.SignedIn;
@@ -58,7 +62,7 @@ export type AuthenticatedState<T extends TokenType = SessionTokenType> = {
   toAuth: ToAuth<T, true>;
 };
 
-export type UnauthenticatedState<T extends TokenType = SessionTokenType> = {
+export type UnauthenticatedState<T extends TokenType | null = SessionTokenType> = {
   status: typeof AuthStatus.SignedOut;
   reason: AuthReason;
   message: string;
@@ -120,8 +124,8 @@ export type AuthErrorReason = (typeof AuthErrorReason)[keyof typeof AuthErrorRea
 
 export type AuthReason = AuthErrorReason | TokenVerificationErrorReason;
 
-export type RequestState<T extends TokenType = SessionTokenType> =
-  | AuthenticatedState<T>
+export type RequestState<T extends TokenType | null = SessionTokenType> =
+  | AuthenticatedState<T extends null ? never : T>
   | UnauthenticatedState<T>
   | (T extends SessionTokenType ? HandshakeState : never);
 
@@ -240,6 +244,29 @@ export function handshake(
   });
 }
 
+export function signedOutInvalidToken(): UnauthenticatedState<null> {
+  const authObject = invalidTokenAuthObject();
+  return {
+    status: AuthStatus.SignedOut,
+    reason: AuthErrorReason.TokenTypeMismatch,
+    message: '',
+    proxyUrl: '',
+    publishableKey: '',
+    isSatellite: false,
+    domain: '',
+    signInUrl: '',
+    signUpUrl: '',
+    afterSignInUrl: '',
+    afterSignUpUrl: '',
+    isSignedIn: false,
+    isAuthenticated: false,
+    tokenType: null,
+    toAuth: () => authObject,
+    headers: new Headers(),
+    token: null,
+  };
+}
+
 const withDebugHeaders = <T extends { headers: Headers; message?: string; reason?: AuthReason; status?: AuthStatus }>(
   requestState: T,
 ): T => {
diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts
index 9b7e5f6c12a..9e012bd54da 100644
--- a/packages/backend/src/tokens/request.ts
+++ b/packages/backend/src/tokens/request.ts
@@ -10,7 +10,7 @@ import type { AuthenticateContext } from './authenticateContext';
 import { createAuthenticateContext } from './authenticateContext';
 import type { SignedInAuthObject } from './authObjects';
 import type { HandshakeState, RequestState, SignedInState, SignedOutState, UnauthenticatedState } from './authStatus';
-import { AuthErrorReason, handshake, signedIn, signedOut } from './authStatus';
+import { AuthErrorReason, handshake, signedIn, signedOut, signedOutInvalidToken } from './authStatus';
 import { createClerkRequest } from './clerkRequest';
 import { getCookieName, getCookieValue } from './cookie';
 import { HandshakeService } from './handshake';
@@ -88,6 +88,23 @@ function checkTokenTypeMismatch(
   return null;
 }
 
+function isTokenTypeInAcceptedArray(
+  acceptsToken: ReadonlyArray<TokenType>,
+  authenticateContext: AuthenticateContext,
+): boolean {
+  let parsedTokenType: TokenType | null = null;
+  const { tokenInHeader } = authenticateContext;
+  if (tokenInHeader) {
+    if (isMachineTokenByPrefix(tokenInHeader)) {
+      parsedTokenType = getMachineTokenType(tokenInHeader);
+    } else {
+      parsedTokenType = TokenType.SessionToken;
+    }
+  }
+  const typeToCheck = parsedTokenType ?? TokenType.SessionToken;
+  return isTokenTypeAccepted(typeToCheck, acceptsToken as TokenType[]);
+}
+
 export interface AuthenticateRequest {
   /**
    * @example
@@ -96,7 +113,7 @@ export interface AuthenticateRequest {
   <T extends readonly TokenType[]>(
     request: Request,
     options: AuthenticateRequestOptions & { acceptsToken: T },
-  ): Promise<RequestState<T[number]>>;
+  ): Promise<RequestState<T[number] | null>>;
 
   /**
    * @example
@@ -123,7 +140,7 @@ export interface AuthenticateRequest {
 export const authenticateRequest: AuthenticateRequest = (async (
   request: Request,
   options: AuthenticateRequestOptions,
-): Promise<RequestState<TokenType>> => {
+): Promise<RequestState<TokenType> | UnauthenticatedState<null>> => {
   const authenticateContext = await createAuthenticateContext(createClerkRequest(request), options);
   assertValidSecretKey(authenticateContext.secretKey);
 
@@ -722,15 +739,20 @@ export const authenticateRequest: AuthenticateRequest = (async (
     });
   }
 
+  // Check if the token type (or session fallback) is accepted
+  if (Array.isArray(acceptsToken)) {
+    if (!isTokenTypeInAcceptedArray(acceptsToken, authenticateContext)) {
+      return signedOutInvalidToken();
+    }
+  }
+
   if (authenticateContext.tokenInHeader) {
     if (acceptsToken === 'any') {
       return authenticateAnyRequestWithTokenInHeader();
     }
-
     if (acceptsToken === TokenType.SessionToken) {
       return authenticateRequestWithTokenInHeader();
     }
-
     return authenticateMachineRequestWithTokenInHeader();
   }
 

From c1d65d5698b604932bfe6589d9dd6e4e8f7ab296 Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sun, 15 Jun 2025 17:03:40 -0700
Subject: [PATCH 04/13] chore: do not verify if not in accepts token array

---
 .../__tests__/getAuthDataFromRequest.test.ts  | 205 +++++++-----------
 .../src/server/data/getAuthDataFromRequest.ts |   5 +
 2 files changed, 87 insertions(+), 123 deletions(-)

diff --git a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
index 25c1d858490..5f50d3549b9 100644
--- a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
+++ b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
@@ -1,7 +1,7 @@
 import type { AuthenticatedMachineObject, SignedOutAuthObject } from '@clerk/backend/internal';
 import { constants, verifyMachineAuthToken } from '@clerk/backend/internal';
 import { NextRequest } from 'next/server';
-import { describe, expect, it, vi } from 'vitest';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
 
 import { getAuthDataFromRequestAsync, getAuthDataFromRequestSync } from '../data/getAuthDataFromRequest';
 
@@ -39,51 +39,16 @@ const machineTokenErrorMock = [
   },
 ];
 
-describe('getAuthDataFromRequestAsync', () => {
-  it('returns unauthenticated machine object when token type does not match', async () => {
-    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
-      data: undefined,
-      tokenType: 'machine_token',
-      errors: [
-        {
-          message: 'Token type mismatch',
-          code: 'token-invalid',
-          status: 401,
-          name: 'MachineTokenVerificationError',
-          getFullMessage: () => 'Token type mismatch',
-        },
-      ],
-    });
-    const req = mockRequest({
-      url: '/api/protected',
-      headers: new Headers({
-        [constants.Headers.Authorization]: 'Bearer ak_xxx',
-      }),
-    });
-
-    const auth = await getAuthDataFromRequestAsync(req, {
-      acceptsToken: 'machine_token',
-    });
+beforeEach(() => {
+  vi.clearAllMocks();
+});
 
-    expect(auth.tokenType).toBe('machine_token');
-    expect((auth as AuthenticatedMachineObject<'machine_token'>).machineId).toBeNull();
-    expect(auth.isAuthenticated).toBe(false);
+describe('getAuthDataFromRequestAsync', () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
   });
 
   it('returns invalid token auth object when token type does not match any in acceptsToken array', async () => {
-    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
-      data: undefined,
-      tokenType: 'machine_token',
-      errors: [
-        {
-          message: 'Token type mismatch',
-          code: 'token-invalid',
-          status: 401,
-          name: 'MachineTokenVerificationError',
-          getFullMessage: () => 'Token type mismatch',
-        },
-      ],
-    });
     const req = mockRequest({
       url: '/api/protected',
       headers: new Headers({
@@ -99,9 +64,9 @@ describe('getAuthDataFromRequestAsync', () => {
     expect(auth.isAuthenticated).toBe(false);
   });
 
-  it('returns authenticated object when token type exists in acceptsToken array', async () => {
+  it('returns authenticated auth obkect for any valid token type', async () => {
     vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
-      data: { id: 'ak_123', subject: 'user_12345' } as any,
+      data: { id: 'ak_id123', subject: 'user_12345' } as any,
       tokenType: 'api_key',
       errors: undefined,
     });
@@ -109,118 +74,112 @@ describe('getAuthDataFromRequestAsync', () => {
     const req = mockRequest({
       url: '/api/protected',
       headers: new Headers({
-        [constants.Headers.Authorization]: 'Bearer ak_123',
+        [constants.Headers.Authorization]: 'Bearer ak_xxx',
       }),
     });
 
-    const auth = await getAuthDataFromRequestAsync(req, {
-      acceptsToken: ['api_key', 'machine_token'],
-    });
+    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'any' });
 
     expect(auth.tokenType).toBe('api_key');
-    expect((auth as AuthenticatedMachineObject<'api_key'>).id).toBe('ak_123');
+    expect((auth as AuthenticatedMachineObject<'api_key'>).id).toBe('ak_id123');
     expect(auth.isAuthenticated).toBe(true);
   });
 
-  it('returns authenticated api_key object when token is valid and acceptsToken is api_key', async () => {
+  it('returns authenticated object when token type exists in acceptsToken array', async () => {
     vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
-      data: { id: 'ak_123', subject: 'user_12345' } as any,
+      data: { id: 'ak_id123', subject: 'user_12345' } as any,
       tokenType: 'api_key',
       errors: undefined,
     });
-    const req = mockRequest({
-      url: '/api/protected',
-      headers: new Headers({
-        [constants.Headers.Authorization]: `Bearer ak_123`,
-      }),
-    });
-    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'api_key' });
-    expect(auth.tokenType).toBe('api_key');
-    expect(auth.isAuthenticated).toBe(true);
-  });
 
-  it('returns authenticated oauth_token object when token is valid and acceptsToken is oauth_token', async () => {
-    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
-      data: { id: 'oat_123', subject: 'user_12345' } as any,
-      tokenType: 'oauth_token',
-      errors: undefined,
-    });
     const req = mockRequest({
       url: '/api/protected',
       headers: new Headers({
-        [constants.Headers.Authorization]: `Bearer oat_123`,
+        [constants.Headers.Authorization]: 'Bearer ak_secret123',
       }),
     });
-    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'oauth_token' });
-    expect(auth.tokenType).toBe('oauth_token');
-    expect(auth.isAuthenticated).toBe(true);
-  });
 
-  it('returns authenticated machine_token object when token is valid and acceptsToken is machine_token', async () => {
-    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
-      data: { id: 'm2m_123', subject: 'mch_123' } as any,
-      tokenType: 'machine_token',
-      errors: undefined,
-    });
-    const req = mockRequest({
-      url: '/api/protected',
-      headers: new Headers({
-        [constants.Headers.Authorization]: `Bearer mt_123`,
-      }),
+    const auth = await getAuthDataFromRequestAsync(req, {
+      acceptsToken: ['api_key', 'machine_token'],
     });
-    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'machine_token' });
-    expect(auth.tokenType).toBe('machine_token');
-    expect(auth.isAuthenticated).toBe(true);
-  });
 
-  it('returns unauthenticated api_key object when token is invalid', async () => {
-    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
-      data: undefined,
-      tokenType: 'api_key',
-      errors: machineTokenErrorMock as any,
-    });
-    const req = mockRequest({
-      url: '/api/protected',
-      headers: new Headers({
-        [constants.Headers.Authorization]: `Bearer ak_123`,
-      }),
-    });
-    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'api_key' });
     expect(auth.tokenType).toBe('api_key');
-    expect(auth.isAuthenticated).toBe(false);
+    expect((auth as AuthenticatedMachineObject<'api_key'>).id).toBe('ak_id123');
+    expect(auth.isAuthenticated).toBe(true);
   });
 
-  it('returns unauthenticated oauth_token object when token is invalid', async () => {
-    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+  it.each([
+    {
+      tokenType: 'api_key' as const,
+      token: 'ak_123',
+      data: { id: 'ak_123', subject: 'user_12345' },
+    },
+    {
+      tokenType: 'oauth_token' as const,
+      token: 'oat_secret123',
+      data: { id: 'oat_id123', subject: 'user_12345' },
+    },
+    {
+      tokenType: 'machine_token' as const,
+      token: 'mt_123',
+      data: { id: 'm2m_123', subject: 'mch_123' },
+    },
+  ])(
+    'returns authenticated $tokenType object when token is valid and acceptsToken is $tokenType',
+    async ({ tokenType, token, data }) => {
+      vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+        data: data as any,
+        tokenType,
+        errors: undefined,
+      });
+
+      const req = mockRequest({
+        url: '/api/protected',
+        headers: new Headers({
+          [constants.Headers.Authorization]: `Bearer ${token}`,
+        }),
+      });
+
+      const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: tokenType });
+
+      expect(auth.tokenType).toBe(tokenType);
+      expect(auth.isAuthenticated).toBe(true);
+    },
+  );
+
+  it.each([
+    {
+      tokenType: 'api_key' as const,
+      token: 'ak_123',
       data: undefined,
-      tokenType: 'oauth_token',
-      errors: machineTokenErrorMock as any,
-    });
-    const req = mockRequest({
-      url: '/api/protected',
-      headers: new Headers({
-        [constants.Headers.Authorization]: `Bearer oat_123`,
-      }),
-    });
-    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'oauth_token' });
-    expect(auth.tokenType).toBe('oauth_token');
-    expect(auth.isAuthenticated).toBe(false);
-  });
-
-  it('returns unauthenticated machine_token object when token is invalid', async () => {
-    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+    },
+    {
+      tokenType: 'oauth_token' as const,
+      token: 'oat_secret123',
+      data: undefined,
+    },
+    {
+      tokenType: 'machine_token' as const,
+      token: 'mt_123',
       data: undefined,
-      tokenType: 'machine_token',
+    },
+  ])('returns unauthenticated $tokenType object when token is invalid', async ({ tokenType, token, data }) => {
+    vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
+      data: data as any,
+      tokenType,
       errors: machineTokenErrorMock as any,
     });
+
     const req = mockRequest({
       url: '/api/protected',
       headers: new Headers({
-        [constants.Headers.Authorization]: `Bearer mt_123`,
+        [constants.Headers.Authorization]: `Bearer ${token}`,
       }),
     });
-    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'machine_token' });
-    expect(auth.tokenType).toBe('machine_token');
+
+    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: tokenType });
+
+    expect(auth.tokenType).toBe(tokenType);
     expect(auth.isAuthenticated).toBe(false);
   });
 
diff --git a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
index 81bcc810047..ed68a5258db 100644
--- a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
+++ b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
@@ -105,6 +105,11 @@ export const getAuthDataFromRequestAsync = async (
   const hasMachineToken = bearerToken && isMachineTokenByPrefix(bearerToken);
   if (hasMachineToken) {
     const machineTokenType = getMachineTokenType(bearerToken);
+
+    if (Array.isArray(acceptsToken) && !acceptsToken.includes(machineTokenType)) {
+      return invalidTokenAuthObject();
+    }
+
     const { data, errors } = await verifyMachineAuthToken(bearerToken, options);
     const authObject = errors
       ? unauthenticatedMachineObject(machineTokenType, options)

From abb388cd5b22e3a522998766ab4073acd047cc27 Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sun, 15 Jun 2025 17:13:32 -0700
Subject: [PATCH 05/13] chore: type clean ups

---
 packages/backend/src/tokens/__tests__/request.test.ts     | 4 ++--
 packages/nextjs/src/server/data/getAuthDataFromRequest.ts | 1 +
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/tokens/__tests__/request.test.ts b/packages/backend/src/tokens/__tests__/request.test.ts
index 48246de34ba..03bd31f006f 100644
--- a/packages/backend/src/tokens/__tests__/request.test.ts
+++ b/packages/backend/src/tokens/__tests__/request.test.ts
@@ -16,7 +16,7 @@ import type { AuthReason } from '../authStatus';
 import { AuthErrorReason, AuthStatus } from '../authStatus';
 import { OrganizationMatcher } from '../organizationMatcher';
 import { authenticateRequest, RefreshTokenErrorReason } from '../request';
-import type { MachineTokenType } from '../tokenTypes';
+import { type MachineTokenType, TokenType } from '../tokenTypes';
 import type { AuthenticateRequestOptions } from '../types';
 
 const PK_TEST = 'pk_test_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA';
@@ -1203,7 +1203,7 @@ describe('tokens.authenticateRequest(options)', () => {
     });
 
     // Test each token type with parameterized tests
-    const tokenTypes = ['api_key', 'oauth_token', 'machine_token'] as const;
+    const tokenTypes = [TokenType.ApiKey, TokenType.OAuthToken, TokenType.MachineToken];
 
     describe.each(tokenTypes)('%s Authentication', tokenType => {
       const mockToken = mockTokens[tokenType];
diff --git a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
index ed68a5258db..eecace15e7c 100644
--- a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
+++ b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
@@ -106,6 +106,7 @@ export const getAuthDataFromRequestAsync = async (
   if (hasMachineToken) {
     const machineTokenType = getMachineTokenType(bearerToken);
 
+    // Early return if the token type is not accepted to save on the verify call
     if (Array.isArray(acceptsToken) && !acceptsToken.includes(machineTokenType)) {
       return invalidTokenAuthObject();
     }

From 46d1a948e8efd3ee3bcf9e1f87d0670e462ae421 Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sun, 15 Jun 2025 17:43:07 -0700
Subject: [PATCH 06/13] chore: add changeset

---
 .changeset/twenty-beds-serve.md                           | 8 ++++++++
 .../src/server/__tests__/getAuthDataFromRequest.test.ts   | 2 +-
 2 files changed, 9 insertions(+), 1 deletion(-)
 create mode 100644 .changeset/twenty-beds-serve.md

diff --git a/.changeset/twenty-beds-serve.md b/.changeset/twenty-beds-serve.md
new file mode 100644
index 00000000000..c70fd6a328a
--- /dev/null
+++ b/.changeset/twenty-beds-serve.md
@@ -0,0 +1,8 @@
+---
+'@clerk/backend': minor
+'@clerk/nextjs': minor
+---
+
+- Optimize `auth()` calls to avoid unnecessary verification calls when the provided token type is not in the `acceptsToken` array.
+- Add handling for invalid token types when `acceptsToken` is an array in `authenticateRequyest()`: now returns a clear unauthenticated state (`tokenType: null`) if the token is not in the accepted list.
+
diff --git a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
index 5f50d3549b9..3e327044276 100644
--- a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
+++ b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
@@ -64,7 +64,7 @@ describe('getAuthDataFromRequestAsync', () => {
     expect(auth.isAuthenticated).toBe(false);
   });
 
-  it('returns authenticated auth obkect for any valid token type', async () => {
+  it('returns authenticated auth object for any valid token type', async () => {
     vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
       data: { id: 'ak_id123', subject: 'user_12345' } as any,
       tokenType: 'api_key',

From 96ec7b611425c030bd9199e04c7fe41ff30622ed Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sun, 15 Jun 2025 17:43:34 -0700
Subject: [PATCH 07/13] chore: remove extra mock clear

---
 .../src/server/__tests__/getAuthDataFromRequest.test.ts       | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
index 3e327044276..c37032b9aba 100644
--- a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
+++ b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
@@ -39,10 +39,6 @@ const machineTokenErrorMock = [
   },
 ];
 
-beforeEach(() => {
-  vi.clearAllMocks();
-});
-
 describe('getAuthDataFromRequestAsync', () => {
   beforeEach(() => {
     vi.clearAllMocks();

From 570c6c7c51d3bbe7ab195d06c392261995e66d6d Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sun, 15 Jun 2025 18:11:03 -0700
Subject: [PATCH 08/13] chore: more tests and clean up

---
 packages/backend/src/tokens/request.ts             | 13 ++-----------
 .../__tests__/getAuthDataFromRequest.test.ts       | 14 ++++++++++++++
 .../src/server/data/getAuthDataFromRequest.ts      |  6 ++++++
 3 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts
index 9e012bd54da..85a425c1b4d 100644
--- a/packages/backend/src/tokens/request.ts
+++ b/packages/backend/src/tokens/request.ts
@@ -669,16 +669,6 @@ export const authenticateRequest: AuthenticateRequest = (async (
       return handleSessionTokenError(new Error('Missing token in header'), 'header');
     }
 
-    // Handle case where tokenType is any and the token is not a machine token
-    if (!isMachineTokenByPrefix(tokenInHeader)) {
-      return signedOut({
-        tokenType: acceptsToken as MachineTokenType,
-        authenticateContext,
-        reason: AuthErrorReason.TokenTypeMismatch,
-        message: '',
-      });
-    }
-
     const parsedTokenType = getMachineTokenType(tokenInHeader);
     const mismatchState = checkTokenTypeMismatch(parsedTokenType, acceptsToken, authenticateContext);
     if (mismatchState) {
@@ -739,7 +729,8 @@ export const authenticateRequest: AuthenticateRequest = (async (
     });
   }
 
-  // Check if the token type (or session fallback) is accepted
+  // If acceptsToken is an array, early check if the token is in the accepted array
+  // to avoid unnecessary verification calls
   if (Array.isArray(acceptsToken)) {
     if (!isTokenTypeInAcceptedArray(acceptsToken, authenticateContext)) {
       return signedOutInvalidToken();
diff --git a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
index c37032b9aba..82135d519d3 100644
--- a/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
+++ b/packages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.ts
@@ -60,6 +60,20 @@ describe('getAuthDataFromRequestAsync', () => {
     expect(auth.isAuthenticated).toBe(false);
   });
 
+  it('returns unauthenticated auth object when token type does not match single acceptsToken', async () => {
+    const req = mockRequest({
+      url: '/api/protected',
+      headers: new Headers({
+        [constants.Headers.Authorization]: 'Bearer ak_xxx',
+      }),
+    });
+
+    const auth = await getAuthDataFromRequestAsync(req, { acceptsToken: 'oauth_token' });
+
+    expect(auth.tokenType).toBe('oauth_token');
+    expect(auth.isAuthenticated).toBe(false);
+  });
+
   it('returns authenticated auth object for any valid token type', async () => {
     vi.mocked(verifyMachineAuthToken).mockResolvedValueOnce({
       data: { id: 'ak_id123', subject: 'user_12345' } as any,
diff --git a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
index eecace15e7c..32090af25a5 100644
--- a/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
+++ b/packages/nextjs/src/server/data/getAuthDataFromRequest.ts
@@ -10,6 +10,7 @@ import {
   invalidTokenAuthObject,
   isMachineTokenByPrefix,
   isTokenTypeAccepted,
+  type MachineTokenType,
   type SignedInAuthObject,
   type SignedOutAuthObject,
   signedOutAuthObject,
@@ -110,6 +111,11 @@ export const getAuthDataFromRequestAsync = async (
     if (Array.isArray(acceptsToken) && !acceptsToken.includes(machineTokenType)) {
       return invalidTokenAuthObject();
     }
+    // Early return for scalar acceptsToken if it does not match the machine token type
+    if (!Array.isArray(acceptsToken) && acceptsToken !== 'any' && machineTokenType !== acceptsToken) {
+      const authObject = unauthenticatedMachineObject(acceptsToken as MachineTokenType, options);
+      return getAuthObjectForAcceptedToken({ authObject, acceptsToken });
+    }
 
     const { data, errors } = await verifyMachineAuthToken(bearerToken, options);
     const authObject = errors

From a1e7ed6d4572faa04bd0a9faf7f517081cadfc0b Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sun, 15 Jun 2025 18:18:21 -0700
Subject: [PATCH 09/13] chore: fix tests

---
 .changeset/twenty-beds-serve.md                      |  2 +-
 .../backend/src/tokens/__tests__/request.test.ts     | 12 +++++-------
 packages/backend/src/tokens/request.ts               | 10 ++++++++++
 3 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/.changeset/twenty-beds-serve.md b/.changeset/twenty-beds-serve.md
index c70fd6a328a..70b563d1baf 100644
--- a/.changeset/twenty-beds-serve.md
+++ b/.changeset/twenty-beds-serve.md
@@ -4,5 +4,5 @@
 ---
 
 - Optimize `auth()` calls to avoid unnecessary verification calls when the provided token type is not in the `acceptsToken` array.
-- Add handling for invalid token types when `acceptsToken` is an array in `authenticateRequyest()`: now returns a clear unauthenticated state (`tokenType: null`) if the token is not in the accepted list.
+- Add handling for invalid token types when `acceptsToken` is an array in `authenticateRequest()`: now returns a clear unauthenticated state (`tokenType: null`) if the token is not in the accepted list.
 
diff --git a/packages/backend/src/tokens/__tests__/request.test.ts b/packages/backend/src/tokens/__tests__/request.test.ts
index 03bd31f006f..8cb17ed8beb 100644
--- a/packages/backend/src/tokens/__tests__/request.test.ts
+++ b/packages/backend/src/tokens/__tests__/request.test.ts
@@ -236,7 +236,7 @@ expect.extend({
   toBeMachineUnauthenticated(
     received,
     expected: {
-      tokenType: MachineTokenType;
+      tokenType: MachineTokenType | null;
       reason: AuthReason;
       message: string;
     },
@@ -246,6 +246,7 @@ expect.extend({
       received.tokenType === expected.tokenType &&
       received.reason === expected.reason &&
       received.message === expected.message &&
+      !received.isAuthenticated &&
       !received.token;
 
     if (pass) {
@@ -264,15 +265,11 @@ expect.extend({
   toBeMachineUnauthenticatedToAuth(
     received,
     expected: {
-      tokenType: MachineTokenType;
+      tokenType: MachineTokenType | null;
     },
   ) {
     const pass =
-      received.tokenType === expected.tokenType &&
-      !received.claims &&
-      !received.subject &&
-      !received.name &&
-      !received.id;
+      received.tokenType === expected.tokenType && !received.isAuthenticated && !received.name && !received.id;
 
     if (pass) {
       return {
@@ -1332,6 +1329,7 @@ describe('tokens.authenticateRequest(options)', () => {
           tokenType: 'machine_token',
           reason: AuthErrorReason.TokenTypeMismatch,
           message: '',
+          isAuthenticated: false,
         });
         expect(result.toAuth()).toBeMachineUnauthenticatedToAuth({
           tokenType: 'machine_token',
diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts
index 85a425c1b4d..a26b4c175f9 100644
--- a/packages/backend/src/tokens/request.ts
+++ b/packages/backend/src/tokens/request.ts
@@ -669,6 +669,16 @@ export const authenticateRequest: AuthenticateRequest = (async (
       return handleSessionTokenError(new Error('Missing token in header'), 'header');
     }
 
+    // Handle case where tokenType is any and the token is not a machine token
+    if (!isMachineTokenByPrefix(tokenInHeader)) {
+      return signedOut({
+        tokenType: acceptsToken as MachineTokenType,
+        authenticateContext,
+        reason: AuthErrorReason.TokenTypeMismatch,
+        message: '',
+      });
+    }
+
     const parsedTokenType = getMachineTokenType(tokenInHeader);
     const mismatchState = checkTokenTypeMismatch(parsedTokenType, acceptsToken, authenticateContext);
     if (mismatchState) {

From efcaa3bc15d3bf0bea7d26cc20bd0ae7771663d5 Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Sun, 15 Jun 2025 18:26:00 -0700
Subject: [PATCH 10/13] chore: pass debug headers to invalid token auth object

---
 packages/backend/src/tokens/authStatus.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/tokens/authStatus.ts b/packages/backend/src/tokens/authStatus.ts
index cefe6f468bb..d0c60eaecbd 100644
--- a/packages/backend/src/tokens/authStatus.ts
+++ b/packages/backend/src/tokens/authStatus.ts
@@ -246,7 +246,7 @@ export function handshake(
 
 export function signedOutInvalidToken(): UnauthenticatedState<null> {
   const authObject = invalidTokenAuthObject();
-  return {
+  return withDebugHeaders({
     status: AuthStatus.SignedOut,
     reason: AuthErrorReason.TokenTypeMismatch,
     message: '',
@@ -264,7 +264,7 @@ export function signedOutInvalidToken(): UnauthenticatedState<null> {
     toAuth: () => authObject,
     headers: new Headers(),
     token: null,
-  };
+  });
 }
 
 const withDebugHeaders = <T extends { headers: Headers; message?: string; reason?: AuthReason; status?: AuthStatus }>(

From e601c280a7ca70a606d9f5dcbab40327a796eb90 Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Mon, 16 Jun 2025 07:01:34 -0700
Subject: [PATCH 11/13] chore: type fixes

---
 packages/backend/src/tokens/request.ts | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts
index a26b4c175f9..d212f568432 100644
--- a/packages/backend/src/tokens/request.ts
+++ b/packages/backend/src/tokens/request.ts
@@ -88,10 +88,7 @@ function checkTokenTypeMismatch(
   return null;
 }
 
-function isTokenTypeInAcceptedArray(
-  acceptsToken: ReadonlyArray<TokenType>,
-  authenticateContext: AuthenticateContext,
-): boolean {
+function isTokenTypeInAcceptedArray(acceptsToken: TokenType[], authenticateContext: AuthenticateContext): boolean {
   let parsedTokenType: TokenType | null = null;
   const { tokenInHeader } = authenticateContext;
   if (tokenInHeader) {
@@ -102,7 +99,7 @@ function isTokenTypeInAcceptedArray(
     }
   }
   const typeToCheck = parsedTokenType ?? TokenType.SessionToken;
-  return isTokenTypeAccepted(typeToCheck, acceptsToken as TokenType[]);
+  return isTokenTypeAccepted(typeToCheck, acceptsToken);
 }
 
 export interface AuthenticateRequest {
@@ -672,7 +669,7 @@ export const authenticateRequest: AuthenticateRequest = (async (
     // Handle case where tokenType is any and the token is not a machine token
     if (!isMachineTokenByPrefix(tokenInHeader)) {
       return signedOut({
-        tokenType: acceptsToken as MachineTokenType,
+        tokenType: acceptsToken as TokenType,
         authenticateContext,
         reason: AuthErrorReason.TokenTypeMismatch,
         message: '',

From bc2074f4c708b8c832094d182142cf1b750c1523 Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Mon, 16 Jun 2025 10:06:52 -0700
Subject: [PATCH 12/13] chore: distributive types in request state

---
 packages/backend/src/tokens/authStatus.ts | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/packages/backend/src/tokens/authStatus.ts b/packages/backend/src/tokens/authStatus.ts
index d0c60eaecbd..46c90a71c4f 100644
--- a/packages/backend/src/tokens/authStatus.ts
+++ b/packages/backend/src/tokens/authStatus.ts
@@ -124,10 +124,12 @@ export type AuthErrorReason = (typeof AuthErrorReason)[keyof typeof AuthErrorRea
 
 export type AuthReason = AuthErrorReason | TokenVerificationErrorReason;
 
-export type RequestState<T extends TokenType | null = SessionTokenType> =
-  | AuthenticatedState<T extends null ? never : T>
-  | UnauthenticatedState<T>
-  | (T extends SessionTokenType ? HandshakeState : never);
+export type RequestState<T extends TokenType | null = SessionTokenType> = T extends any
+  ?
+      | AuthenticatedState<T extends null ? never : T>
+      | UnauthenticatedState<T>
+      | (T extends SessionTokenType ? HandshakeState : never)
+  : never;
 
 type BaseSignedInParams = {
   authenticateContext: AuthenticateContext;

From afbb6d9858bbffa8062a6293481ed830aeb6ccf6 Mon Sep 17 00:00:00 2001
From: wobsoriano <sorianorobertc@gmail.com>
Date: Mon, 16 Jun 2025 10:23:55 -0700
Subject: [PATCH 13/13] chore: revert request state types

---
 packages/backend/src/tokens/authStatus.ts | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/packages/backend/src/tokens/authStatus.ts b/packages/backend/src/tokens/authStatus.ts
index 46c90a71c4f..d0c60eaecbd 100644
--- a/packages/backend/src/tokens/authStatus.ts
+++ b/packages/backend/src/tokens/authStatus.ts
@@ -124,12 +124,10 @@ export type AuthErrorReason = (typeof AuthErrorReason)[keyof typeof AuthErrorRea
 
 export type AuthReason = AuthErrorReason | TokenVerificationErrorReason;
 
-export type RequestState<T extends TokenType | null = SessionTokenType> = T extends any
-  ?
-      | AuthenticatedState<T extends null ? never : T>
-      | UnauthenticatedState<T>
-      | (T extends SessionTokenType ? HandshakeState : never)
-  : never;
+export type RequestState<T extends TokenType | null = SessionTokenType> =
+  | AuthenticatedState<T extends null ? never : T>
+  | UnauthenticatedState<T>
+  | (T extends SessionTokenType ? HandshakeState : never);
 
 type BaseSignedInParams = {
   authenticateContext: AuthenticateContext;