From bdc16ac73f7c1dcc16e75425e54dc301284b40ff Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 24 Jul 2025 15:57:38 -0700 Subject: [PATCH 01/10] feat(nuxt): Introduce machine auth --- packages/nuxt/src/module.ts | 10 +++++----- packages/nuxt/src/runtime/server/clerkMiddleware.ts | 13 ++++++++++--- packages/nuxt/src/runtime/server/getAuth.ts | 10 ++++++---- packages/nuxt/src/runtime/server/utils.ts | 4 ++-- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 8863eccd5f0..e7cad4b701f 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -110,13 +110,13 @@ export default defineNuxtModule({ { filename: 'types/clerk.d.ts', getContents: () => `import type { SessionAuthObject } from '@clerk/backend'; - declare module 'h3' { - type AuthObjectHandler = SessionAuthObject & { - (): SessionAuthObject; - } + import type { AuthenticateRequestOptions, GetAuthFn } from '@clerk/backend/internal'; + import type { H3Event } from 'h3'; + type GetAuthOptions = { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] }; + declare module 'h3' { interface H3EventContext { - auth: AuthObjectHandler; + auth: SessionAuthObject & GetAuthFn; } } `, diff --git a/packages/nuxt/src/runtime/server/clerkMiddleware.ts b/packages/nuxt/src/runtime/server/clerkMiddleware.ts index d1775655a3e..8397db3c4f7 100644 --- a/packages/nuxt/src/runtime/server/clerkMiddleware.ts +++ b/packages/nuxt/src/runtime/server/clerkMiddleware.ts @@ -1,11 +1,12 @@ import type { AuthenticateRequestOptions } from '@clerk/backend/internal'; -import { AuthStatus, constants } from '@clerk/backend/internal'; +import { AuthStatus, constants, getAuthObjectForAcceptedToken, TokenType } from '@clerk/backend/internal'; import { deprecated } from '@clerk/shared/deprecated'; import { handleNetlifyCacheInDevInstance } from '@clerk/shared/netlifyCacheHandler'; import type { EventHandler } from 'h3'; import { createError, eventHandler, setResponseHeader } from 'h3'; import { clerkClient } from './clerkClient'; +import type { GetAuthOptions } from './getAuth'; import { createInitialState, toWebRequest } from './utils'; function parseHandlerAndOptions(args: unknown[]) { @@ -81,7 +82,10 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { return eventHandler(async event => { const clerkRequest = toWebRequest(event); - const requestState = await clerkClient(event).authenticateRequest(clerkRequest, options); + const requestState = await clerkClient(event).authenticateRequest(clerkRequest, { + ...options, + acceptsToken: 'any', + }); const locationHeader = requestState.headers.get(constants.Headers.Location); if (locationHeader) { @@ -105,7 +109,10 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { } const authObject = requestState.toAuth(); - const authHandler = () => authObject; + const authHandler = (options?: GetAuthOptions) => { + const acceptsToken = options?.acceptsToken ?? TokenType.SessionToken; + return getAuthObjectForAcceptedToken({ authObject, acceptsToken }); + }; const auth = new Proxy(Object.assign(authHandler, authObject), { get(target, prop: string, receiver) { diff --git a/packages/nuxt/src/runtime/server/getAuth.ts b/packages/nuxt/src/runtime/server/getAuth.ts index c62d43ce5c1..7b03003dca7 100644 --- a/packages/nuxt/src/runtime/server/getAuth.ts +++ b/packages/nuxt/src/runtime/server/getAuth.ts @@ -1,14 +1,16 @@ -import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend/internal'; +import type { AuthenticateRequestOptions, GetAuthFn } from '@clerk/backend/internal'; import type { H3Event } from 'h3'; import { moduleRegistrationRequired } from './errors'; -export function getAuth(event: H3Event): SignedInAuthObject | SignedOutAuthObject { - const authObject = event.context.auth(); +export type GetAuthOptions = { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] }; + +export const getAuth: GetAuthFn = ((event: H3Event, options?: GetAuthOptions) => { + const authObject = event.context.auth(options); if (!authObject) { throw new Error(moduleRegistrationRequired); } return authObject; -} +}) as GetAuthFn; diff --git a/packages/nuxt/src/runtime/server/utils.ts b/packages/nuxt/src/runtime/server/utils.ts index 41aaf18a2ad..efc87424bde 100644 --- a/packages/nuxt/src/runtime/server/utils.ts +++ b/packages/nuxt/src/runtime/server/utils.ts @@ -1,4 +1,4 @@ -import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend/internal'; +import type { AuthObject } from '@clerk/backend'; import { makeAuthObjectSerializable, stripPrivateDataFromObject } from '@clerk/backend/internal'; import type { InitialState } from '@clerk/types'; import type { H3Event } from 'h3'; @@ -17,7 +17,7 @@ export function toWebRequest(event: H3Event) { }); } -export function createInitialState(auth: SignedInAuthObject | SignedOutAuthObject) { +export function createInitialState(auth: AuthObject) { const initialState = makeAuthObjectSerializable(stripPrivateDataFromObject(auth)); return initialState as unknown as InitialState; } From 2eede943e7736620b753f411ad762985f1b02d38 Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Thu, 24 Jul 2025 15:58:41 -0700 Subject: [PATCH 02/10] chore: add changeset --- .changeset/cool-guests-trade.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cool-guests-trade.md diff --git a/.changeset/cool-guests-trade.md b/.changeset/cool-guests-trade.md new file mode 100644 index 00000000000..573aa99b4af --- /dev/null +++ b/.changeset/cool-guests-trade.md @@ -0,0 +1,5 @@ +--- +"@clerk/nuxt": minor +--- + +Introduce machine auth From d35e5606ce2fa4bfecaa94f65603e039ea791341 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 25 Jul 2025 10:46:45 -0700 Subject: [PATCH 03/10] chore: improve types --- .../src/runtime/server/clerkMiddleware.ts | 6 +-- packages/nuxt/src/runtime/server/getAuth.ts | 7 ++-- packages/nuxt/src/runtime/server/types.ts | 42 +++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 packages/nuxt/src/runtime/server/types.ts diff --git a/packages/nuxt/src/runtime/server/clerkMiddleware.ts b/packages/nuxt/src/runtime/server/clerkMiddleware.ts index 8397db3c4f7..c2b30dcd6b7 100644 --- a/packages/nuxt/src/runtime/server/clerkMiddleware.ts +++ b/packages/nuxt/src/runtime/server/clerkMiddleware.ts @@ -6,7 +6,7 @@ import type { EventHandler } from 'h3'; import { createError, eventHandler, setResponseHeader } from 'h3'; import { clerkClient } from './clerkClient'; -import type { GetAuthOptions } from './getAuth'; +import type { AuthFn, AuthOptions } from './types'; import { createInitialState, toWebRequest } from './utils'; function parseHandlerAndOptions(args: unknown[]) { @@ -109,10 +109,10 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { } const authObject = requestState.toAuth(); - const authHandler = (options?: GetAuthOptions) => { + const authHandler: AuthFn = ((options?: AuthOptions) => { const acceptsToken = options?.acceptsToken ?? TokenType.SessionToken; return getAuthObjectForAcceptedToken({ authObject, acceptsToken }); - }; + }) as AuthFn; const auth = new Proxy(Object.assign(authHandler, authObject), { get(target, prop: string, receiver) { diff --git a/packages/nuxt/src/runtime/server/getAuth.ts b/packages/nuxt/src/runtime/server/getAuth.ts index 7b03003dca7..1ed76255dcd 100644 --- a/packages/nuxt/src/runtime/server/getAuth.ts +++ b/packages/nuxt/src/runtime/server/getAuth.ts @@ -1,11 +1,10 @@ -import type { AuthenticateRequestOptions, GetAuthFn } from '@clerk/backend/internal'; +import type { GetAuthFn } from '@clerk/backend/internal'; import type { H3Event } from 'h3'; import { moduleRegistrationRequired } from './errors'; +import type { AuthOptions } from './types'; -export type GetAuthOptions = { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] }; - -export const getAuth: GetAuthFn = ((event: H3Event, options?: GetAuthOptions) => { +export const getAuth: GetAuthFn = ((event: H3Event, options?: AuthOptions) => { const authObject = event.context.auth(options); if (!authObject) { diff --git a/packages/nuxt/src/runtime/server/types.ts b/packages/nuxt/src/runtime/server/types.ts new file mode 100644 index 00000000000..f5a82359ef5 --- /dev/null +++ b/packages/nuxt/src/runtime/server/types.ts @@ -0,0 +1,42 @@ +import type { AuthObject, InvalidTokenAuthObject, MachineAuthObject, SessionAuthObject } from '@clerk/backend'; +import type { + AuthenticateRequestOptions, + InferAuthObjectFromToken, + InferAuthObjectFromTokenArray, + SessionTokenType, + TokenType, +} from '@clerk/backend/internal'; + +export type AuthOptions = { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] }; + +export interface AuthFn { + /** + * @example + * const auth = event.locals.auth({ acceptsToken: ['session_token', 'api_key'] }) + */ + ( + options: AuthOptions & { acceptsToken: T }, + ): + | InferAuthObjectFromTokenArray>> + | InvalidTokenAuthObject; + + /** + * @example + * const auth = event.locals.auth({ acceptsToken: 'session_token' }) + */ + ( + options: AuthOptions & { acceptsToken: T }, + ): InferAuthObjectFromToken>>; + + /** + * @example + * const auth = event.locals.auth({ acceptsToken: 'any' }) + */ + (options: AuthOptions & { acceptsToken: 'any' }): AuthObject; + + /** + * @example + * const auth = event.locals.auth() + */ + (): SessionAuthObject; +} From 5a6fa5f384fb803a87f0e06d9335c1f9a83dc6c6 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 25 Jul 2025 11:07:09 -0700 Subject: [PATCH 04/10] chore: improve types --- packages/nuxt/src/module.ts | 6 ++---- packages/nuxt/src/runtime/server/getAuth.ts | 15 ++++++++++----- packages/nuxt/src/runtime/server/types.ts | 8 ++++---- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index e7cad4b701f..4752161ad27 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -110,13 +110,11 @@ export default defineNuxtModule({ { filename: 'types/clerk.d.ts', getContents: () => `import type { SessionAuthObject } from '@clerk/backend'; - import type { AuthenticateRequestOptions, GetAuthFn } from '@clerk/backend/internal'; - import type { H3Event } from 'h3'; - type GetAuthOptions = { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] }; + import type { AuthFn } from ${JSON.stringify(resolver.resolve('./runtime/server/types'))} declare module 'h3' { interface H3EventContext { - auth: SessionAuthObject & GetAuthFn; + auth: SessionAuthObject & AuthFn; } } `, diff --git a/packages/nuxt/src/runtime/server/getAuth.ts b/packages/nuxt/src/runtime/server/getAuth.ts index 1ed76255dcd..7b7e0d49629 100644 --- a/packages/nuxt/src/runtime/server/getAuth.ts +++ b/packages/nuxt/src/runtime/server/getAuth.ts @@ -1,15 +1,20 @@ -import type { GetAuthFn } from '@clerk/backend/internal'; +import type { SessionAuthObject } from '@clerk/backend'; +import { deprecated } from '@clerk/shared/deprecated'; import type { H3Event } from 'h3'; import { moduleRegistrationRequired } from './errors'; -import type { AuthOptions } from './types'; -export const getAuth: GetAuthFn = ((event: H3Event, options?: AuthOptions) => { - const authObject = event.context.auth(options); +/** + * @deprecated Use `event.context.auth()` instead. + */ +export function getAuth(event: H3Event): SessionAuthObject { + deprecated('getAuth', 'Use `event.context.auth()` instead.'); + + const authObject = event.context.auth(); if (!authObject) { throw new Error(moduleRegistrationRequired); } return authObject; -}) as GetAuthFn; +} diff --git a/packages/nuxt/src/runtime/server/types.ts b/packages/nuxt/src/runtime/server/types.ts index f5a82359ef5..baf989030c9 100644 --- a/packages/nuxt/src/runtime/server/types.ts +++ b/packages/nuxt/src/runtime/server/types.ts @@ -12,7 +12,7 @@ export type AuthOptions = { acceptsToken?: AuthenticateRequestOptions['acceptsTo export interface AuthFn { /** * @example - * const auth = event.locals.auth({ acceptsToken: ['session_token', 'api_key'] }) + * const auth = event.context.auth({ acceptsToken: ['session_token', 'api_key'] }) */ ( options: AuthOptions & { acceptsToken: T }, @@ -22,7 +22,7 @@ export interface AuthFn { /** * @example - * const auth = event.locals.auth({ acceptsToken: 'session_token' }) + * const auth = event.context.auth({ acceptsToken: 'session_token' }) */ ( options: AuthOptions & { acceptsToken: T }, @@ -30,13 +30,13 @@ export interface AuthFn { /** * @example - * const auth = event.locals.auth({ acceptsToken: 'any' }) + * const auth = event.context.auth({ acceptsToken: 'any' }) */ (options: AuthOptions & { acceptsToken: 'any' }): AuthObject; /** * @example - * const auth = event.locals.auth() + * const auth = event.context.auth() */ (): SessionAuthObject; } From 992e75655aa9d11db2a0751ae28bcd2626e2e38f Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 25 Jul 2025 11:25:35 -0700 Subject: [PATCH 05/10] chore: use conventional exports --- packages/nuxt/src/module.ts | 2 +- packages/nuxt/src/runtime/server/index.ts | 1 + packages/nuxt/src/runtime/server/types.ts | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 4752161ad27..84fa45cb53f 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -110,7 +110,7 @@ export default defineNuxtModule({ { filename: 'types/clerk.d.ts', getContents: () => `import type { SessionAuthObject } from '@clerk/backend'; - import type { AuthFn } from ${JSON.stringify(resolver.resolve('./runtime/server/types'))} + import type { AuthFn } from '@clerk/nuxt/server'; declare module 'h3' { interface H3EventContext { diff --git a/packages/nuxt/src/runtime/server/index.ts b/packages/nuxt/src/runtime/server/index.ts index 83788296a35..99477922906 100644 --- a/packages/nuxt/src/runtime/server/index.ts +++ b/packages/nuxt/src/runtime/server/index.ts @@ -3,3 +3,4 @@ export { clerkClient } from './clerkClient'; export { clerkMiddleware } from './clerkMiddleware'; export { createRouteMatcher } from './routeMatcher'; export { getAuth } from './getAuth'; +export type { AuthFn } from './types'; diff --git a/packages/nuxt/src/runtime/server/types.ts b/packages/nuxt/src/runtime/server/types.ts index baf989030c9..f44e68ce892 100644 --- a/packages/nuxt/src/runtime/server/types.ts +++ b/packages/nuxt/src/runtime/server/types.ts @@ -9,6 +9,9 @@ import type { export type AuthOptions = { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] }; +/** + * @internal This type is used to define the `auth` function in the event context. + */ export interface AuthFn { /** * @example From fb59ccfb1f9b6d45648a2591a855a971e37eccb3 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 25 Jul 2025 11:48:53 -0700 Subject: [PATCH 06/10] fix proxy issue --- packages/nuxt/src/runtime/server/clerkMiddleware.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/nuxt/src/runtime/server/clerkMiddleware.ts b/packages/nuxt/src/runtime/server/clerkMiddleware.ts index c2b30dcd6b7..9fd49724e57 100644 --- a/packages/nuxt/src/runtime/server/clerkMiddleware.ts +++ b/packages/nuxt/src/runtime/server/clerkMiddleware.ts @@ -114,11 +114,13 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { return getAuthObjectForAcceptedToken({ authObject, acceptsToken }); }) as AuthFn; - const auth = new Proxy(Object.assign(authHandler, authObject), { - get(target, prop: string, receiver) { + const auth = new Proxy(authHandler, { + get(target, prop, receiver) { deprecated('event.context.auth', 'Use `event.context.auth()` as a function instead.'); - - return Reflect.get(target, prop, receiver); + // If the property exists on the function, return it + if (prop in target) return Reflect.get(target, prop, receiver); + // Otherwise, get it from the authObject + return authObject?.[prop as keyof typeof authObject]; }, }); From ae612112d44575a9b6c2f9cedeffdb19bebe2e80 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 25 Jul 2025 12:13:34 -0700 Subject: [PATCH 07/10] fix tests --- .../server/__tests__/clerkMiddleware.test.ts | 15 ++++++++++++++- .../nuxt/src/runtime/server/clerkMiddleware.ts | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/nuxt/src/runtime/server/__tests__/clerkMiddleware.test.ts b/packages/nuxt/src/runtime/server/__tests__/clerkMiddleware.test.ts index 03b8afdb2bf..64ab11ed05c 100644 --- a/packages/nuxt/src/runtime/server/__tests__/clerkMiddleware.test.ts +++ b/packages/nuxt/src/runtime/server/__tests__/clerkMiddleware.test.ts @@ -6,6 +6,16 @@ import { clerkMiddleware } from '../clerkMiddleware'; const AUTH_RESPONSE = { userId: 'user_2jZSstSbxtTndD9P7q4kDl0VVZa', sessionId: 'sess_2jZSstSbxtTndD9P7q4kDl0VVZa', + tokenType: 'session_token', + isAuthenticated: true, + sessionStatus: 'active', + sessionClaims: {}, + actor: null, + factorVerificationAge: null, + orgId: null, + orgRole: null, + orgSlug: null, + orgPermissions: null, }; const MOCK_OPTIONS = { @@ -22,7 +32,10 @@ vi.mock('#imports', () => { }); const authenticateRequestMock = vi.fn().mockResolvedValue({ - toAuth: () => AUTH_RESPONSE, + toAuth: () => { + console.log('Mock toAuth() called, returning:', AUTH_RESPONSE); + return AUTH_RESPONSE; + }, headers: new Headers(), }); diff --git a/packages/nuxt/src/runtime/server/clerkMiddleware.ts b/packages/nuxt/src/runtime/server/clerkMiddleware.ts index 9fd49724e57..435fdc4270e 100644 --- a/packages/nuxt/src/runtime/server/clerkMiddleware.ts +++ b/packages/nuxt/src/runtime/server/clerkMiddleware.ts @@ -109,6 +109,7 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { } const authObject = requestState.toAuth(); + console.log('authObject', authObject); const authHandler: AuthFn = ((options?: AuthOptions) => { const acceptsToken = options?.acceptsToken ?? TokenType.SessionToken; return getAuthObjectForAcceptedToken({ authObject, acceptsToken }); From dcb2952d9d8f98cf60d855901336e221e6890b0a Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 25 Jul 2025 12:24:20 -0700 Subject: [PATCH 08/10] test: update middleware test to include machine auth --- .../server/__tests__/clerkMiddleware.test.ts | 129 ++++++++++++++++-- .../src/runtime/server/clerkMiddleware.ts | 1 - 2 files changed, 120 insertions(+), 10 deletions(-) diff --git a/packages/nuxt/src/runtime/server/__tests__/clerkMiddleware.test.ts b/packages/nuxt/src/runtime/server/__tests__/clerkMiddleware.test.ts index 64ab11ed05c..9dd99f6d34f 100644 --- a/packages/nuxt/src/runtime/server/__tests__/clerkMiddleware.test.ts +++ b/packages/nuxt/src/runtime/server/__tests__/clerkMiddleware.test.ts @@ -3,7 +3,7 @@ import { vi } from 'vitest'; import { clerkMiddleware } from '../clerkMiddleware'; -const AUTH_RESPONSE = { +const SESSION_AUTH_RESPONSE = { userId: 'user_2jZSstSbxtTndD9P7q4kDl0VVZa', sessionId: 'sess_2jZSstSbxtTndD9P7q4kDl0VVZa', tokenType: 'session_token', @@ -18,6 +18,18 @@ const AUTH_RESPONSE = { orgPermissions: null, }; +const MACHINE_AUTH_RESPONSE = { + id: 'ak_123456789', + subject: 'user_2jZSstSbxtTndD9P7q4kDl0VVZa', + scopes: ['read:users', 'write:users'], + tokenType: 'api_key', + isAuthenticated: true, + name: 'Test API Key', + claims: { custom: 'claim' }, + userId: 'user_2jZSstSbxtTndD9P7q4kDl0VVZa', + orgId: null, +}; + const MOCK_OPTIONS = { secretKey: 'sk_test_xxxxxxxxxxxxxxxxxx', publishableKey: 'pk_test_xxxxxxxxxxxxx', @@ -32,10 +44,7 @@ vi.mock('#imports', () => { }); const authenticateRequestMock = vi.fn().mockResolvedValue({ - toAuth: () => { - console.log('Mock toAuth() called, returning:', AUTH_RESPONSE); - return AUTH_RESPONSE; - }, + toAuth: () => SESSION_AUTH_RESPONSE, headers: new Headers(), }); @@ -60,7 +69,7 @@ describe('clerkMiddleware(params)', () => { const response = await handler(new Request(new URL('/', 'http://localhost'))); expect(response.status).toBe(200); - expect(await response.json()).toEqual(AUTH_RESPONSE); + expect(await response.json()).toEqual(SESSION_AUTH_RESPONSE); }); test('renders route as normally when used with options param', async () => { @@ -75,7 +84,7 @@ describe('clerkMiddleware(params)', () => { expect(response.status).toBe(200); expect(authenticateRequestMock).toHaveBeenCalledWith(expect.any(Request), expect.objectContaining(MOCK_OPTIONS)); - expect(await response.json()).toEqual(AUTH_RESPONSE); + expect(await response.json()).toEqual(SESSION_AUTH_RESPONSE); }); test('executes handler and renders route when used with a custom handler', async () => { @@ -94,7 +103,7 @@ describe('clerkMiddleware(params)', () => { expect(response.status).toBe(200); expect(response.headers.get('a-custom-header')).toBe('1'); - expect(await response.json()).toEqual(AUTH_RESPONSE); + expect(await response.json()).toEqual(SESSION_AUTH_RESPONSE); }); test('executes handler and renders route when used with a custom handler and options', async () => { @@ -114,6 +123,108 @@ describe('clerkMiddleware(params)', () => { expect(response.status).toBe(200); expect(response.headers.get('a-custom-header')).toBe('1'); expect(authenticateRequestMock).toHaveBeenCalledWith(expect.any(Request), expect.objectContaining(MOCK_OPTIONS)); - expect(await response.json()).toEqual(AUTH_RESPONSE); + expect(await response.json()).toEqual(SESSION_AUTH_RESPONSE); + }); + + describe('machine authentication', () => { + test('returns machine auth object when acceptsToken is machine token type', async () => { + authenticateRequestMock.mockResolvedValueOnce({ + toAuth: () => MACHINE_AUTH_RESPONSE, + headers: new Headers(), + }); + + const app = createApp(); + const handler = toWebHandler(app); + app.use(clerkMiddleware()); + app.use( + '/', + eventHandler(event => event.context.auth({ acceptsToken: 'api_key' })), + ); + const response = await handler(new Request(new URL('/', 'http://localhost'))); + + expect(response.status).toBe(200); + expect(await response.json()).toEqual(MACHINE_AUTH_RESPONSE); + }); + + test('returns machine auth object when acceptsToken array includes machine token type', async () => { + authenticateRequestMock.mockResolvedValueOnce({ + toAuth: () => MACHINE_AUTH_RESPONSE, + headers: new Headers(), + }); + + const app = createApp(); + const handler = toWebHandler(app); + app.use(clerkMiddleware()); + app.use( + '/', + eventHandler(event => event.context.auth({ acceptsToken: ['session_token', 'api_key'] })), + ); + const response = await handler(new Request(new URL('/', 'http://localhost'))); + + expect(response.status).toBe(200); + expect(await response.json()).toEqual(MACHINE_AUTH_RESPONSE); + }); + + test('returns any auth object when acceptsToken is any', async () => { + authenticateRequestMock.mockResolvedValueOnce({ + toAuth: () => MACHINE_AUTH_RESPONSE, + headers: new Headers(), + }); + + const app = createApp(); + const handler = toWebHandler(app); + app.use(clerkMiddleware()); + app.use( + '/', + eventHandler(event => event.context.auth({ acceptsToken: 'any' })), + ); + const response = await handler(new Request(new URL('/', 'http://localhost'))); + + expect(response.status).toBe(200); + expect(await response.json()).toEqual(MACHINE_AUTH_RESPONSE); + }); + + test('returns unauthenticated machine object when token type does not match acceptsToken', async () => { + authenticateRequestMock.mockResolvedValueOnce({ + toAuth: () => MACHINE_AUTH_RESPONSE, + headers: new Headers(), + }); + + const app = createApp(); + const handler = toWebHandler(app); + app.use(clerkMiddleware()); + app.use( + '/', + eventHandler(event => event.context.auth({ acceptsToken: 'machine_token' })), + ); + const response = await handler(new Request(new URL('/', 'http://localhost'))); + + expect(response.status).toBe(200); + const result = await response.json(); + expect(result.tokenType).toBe('machine_token'); + expect(result.isAuthenticated).toBe(false); + expect(result.id).toBe(null); + }); + + test('returns invalid token object when token type is not in acceptsToken array', async () => { + authenticateRequestMock.mockResolvedValueOnce({ + toAuth: () => MACHINE_AUTH_RESPONSE, + headers: new Headers(), + }); + + const app = createApp(); + const handler = toWebHandler(app); + app.use(clerkMiddleware()); + app.use( + '/', + eventHandler(event => event.context.auth({ acceptsToken: ['session_token', 'machine_token'] })), + ); + const response = await handler(new Request(new URL('/', 'http://localhost'))); + + expect(response.status).toBe(200); + const result = await response.json(); + expect(result.tokenType).toBe(null); + expect(result.isAuthenticated).toBe(false); + }); }); }); diff --git a/packages/nuxt/src/runtime/server/clerkMiddleware.ts b/packages/nuxt/src/runtime/server/clerkMiddleware.ts index 435fdc4270e..9fd49724e57 100644 --- a/packages/nuxt/src/runtime/server/clerkMiddleware.ts +++ b/packages/nuxt/src/runtime/server/clerkMiddleware.ts @@ -109,7 +109,6 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { } const authObject = requestState.toAuth(); - console.log('authObject', authObject); const authHandler: AuthFn = ((options?: AuthOptions) => { const acceptsToken = options?.acceptsToken ?? TokenType.SessionToken; return getAuthObjectForAcceptedToken({ authObject, acceptsToken }); From 1709c637980130462cea2ee64ab98c72b8c4fff7 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 25 Jul 2025 12:29:20 -0700 Subject: [PATCH 09/10] test: add auth local type test --- .../runtime/server/__tests__/auth.test-d.ts | 56 +++++++++++++++++++ packages/nuxt/vitest.config.ts | 3 + 2 files changed, 59 insertions(+) create mode 100644 packages/nuxt/src/runtime/server/__tests__/auth.test-d.ts diff --git a/packages/nuxt/src/runtime/server/__tests__/auth.test-d.ts b/packages/nuxt/src/runtime/server/__tests__/auth.test-d.ts new file mode 100644 index 00000000000..4f9e79c7f29 --- /dev/null +++ b/packages/nuxt/src/runtime/server/__tests__/auth.test-d.ts @@ -0,0 +1,56 @@ +import type { AuthObject, InvalidTokenAuthObject, MachineAuthObject, SessionAuthObject } from '@clerk/backend'; +import { expectTypeOf, test } from 'vitest'; + +import type { AuthFn } from '../types'; + +test('infers the correct AuthObject type for each accepted token type', () => { + // Mock event object + const event = { + locals: { + auth: (() => {}) as AuthFn, + }, + }; + + // Session token by default + expectTypeOf(event.locals.auth()).toMatchTypeOf(); + + // Individual token types + expectTypeOf(event.locals.auth({ acceptsToken: 'session_token' })).toMatchTypeOf(); + expectTypeOf(event.locals.auth({ acceptsToken: 'api_key' })).toMatchTypeOf>(); + expectTypeOf(event.locals.auth({ acceptsToken: 'machine_token' })).toMatchTypeOf< + MachineAuthObject<'machine_token'> + >(); + expectTypeOf(event.locals.auth({ acceptsToken: 'oauth_token' })).toMatchTypeOf>(); + + // Array of token types + expectTypeOf(event.locals.auth({ acceptsToken: ['session_token', 'machine_token'] })).toMatchTypeOf< + SessionAuthObject | MachineAuthObject<'machine_token'> | InvalidTokenAuthObject + >(); + expectTypeOf(event.locals.auth({ acceptsToken: ['machine_token', 'oauth_token'] })).toMatchTypeOf< + MachineAuthObject<'machine_token' | 'oauth_token'> | InvalidTokenAuthObject + >(); + + // Any token type + expectTypeOf(event.locals.auth({ acceptsToken: 'any' })).toMatchTypeOf(); +}); + +test('verifies discriminated union works correctly with acceptsToken: any', () => { + // Mock event object + const event = { + locals: { + auth: (() => {}) as AuthFn, + }, + }; + + const auth = event.locals.auth({ acceptsToken: 'any' }); + + if (auth.tokenType === 'session_token') { + expectTypeOf(auth).toMatchTypeOf(); + } else if (auth.tokenType === 'api_key') { + expectTypeOf(auth).toMatchTypeOf>(); + } else if (auth.tokenType === 'machine_token') { + expectTypeOf(auth).toMatchTypeOf>(); + } else if (auth.tokenType === 'oauth_token') { + expectTypeOf(auth).toMatchTypeOf>(); + } +}); diff --git a/packages/nuxt/vitest.config.ts b/packages/nuxt/vitest.config.ts index 7382f40e7d2..0183bf23242 100644 --- a/packages/nuxt/vitest.config.ts +++ b/packages/nuxt/vitest.config.ts @@ -2,6 +2,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + typecheck: { + enabled: true, + }, globals: true, }, }); From eac3bdfd677d91360bec33522ee24d64f69a181a Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 25 Jul 2025 12:38:10 -0700 Subject: [PATCH 10/10] chore: update changeset --- .changeset/cool-guests-trade.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.changeset/cool-guests-trade.md b/.changeset/cool-guests-trade.md index 573aa99b4af..6ff41bd66da 100644 --- a/.changeset/cool-guests-trade.md +++ b/.changeset/cool-guests-trade.md @@ -1,5 +1,24 @@ --- -"@clerk/nuxt": minor +'@clerk/nuxt': minor --- -Introduce machine auth +Introduces machine authentication, supporting four token types: `api_key`, `oauth_token`, `machine_token`, and `session_token`. For backwards compatibility, `session_token` remains the default when no token type is specified. This enables machine-to-machine authentication and use cases such as API keys and OAuth integrations. Existing applications continue to work without modification. + +You can specify which token types are allowed by using the `acceptsToken` option in the `event.context.auth()` context. This option can be set to a specific type, an array of types, or `'any'` to accept all supported tokens. + +Example usage: + +```ts +export default eventHandler((event) => { + const auth = event.locals.auth({ acceptsToken: 'any' }) + + if (authObject.tokenType === 'session_token') { + console.log('this is session token from a user') + } else { + console.log('this is some other type of machine token') + console.log('more specifically, a ' + authObject.tokenType) + } + + return {} +}) +``` \ No newline at end of file