Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cool-guests-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/nuxt": minor
---

Introduce machine auth
8 changes: 3 additions & 5 deletions packages/nuxt/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,11 @@ export default defineNuxtModule<ModuleOptions>({
{
filename: 'types/clerk.d.ts',
getContents: () => `import type { SessionAuthObject } from '@clerk/backend';
declare module 'h3' {
type AuthObjectHandler = SessionAuthObject & {
(): SessionAuthObject;
}
import type { AuthFn } from '@clerk/nuxt/server';

declare module 'h3' {
interface H3EventContext {
auth: AuthObjectHandler;
auth: SessionAuthObject & AuthFn;
}
}
`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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(),
});

Expand Down
24 changes: 17 additions & 7 deletions packages/nuxt/src/runtime/server/clerkMiddleware.ts
Original file line number Diff line number Diff line change
@@ -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 { AuthFn, AuthOptions } from './types';
import { createInitialState, toWebRequest } from './utils';

function parseHandlerAndOptions(args: unknown[]) {
Expand Down Expand Up @@ -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',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the option that enables verification of api keys, oauth tokens and machine tokens

});

const locationHeader = requestState.headers.get(constants.Headers.Location);
if (locationHeader) {
Expand All @@ -105,13 +109,19 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => {
}

const authObject = requestState.toAuth();
const authHandler = () => authObject;
console.log('authObject', authObject);
const authHandler: AuthFn = ((options?: AuthOptions) => {
const acceptsToken = options?.acceptsToken ?? TokenType.SessionToken;
return getAuthObjectForAcceptedToken({ authObject, acceptsToken });
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a shared helper across our SDKs to get the actual auth object based on the acceptsToken value. It is unit tested there.

}) 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];
},
});

Expand Down
10 changes: 8 additions & 2 deletions packages/nuxt/src/runtime/server/getAuth.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import type { SignedInAuthObject, SignedOutAuthObject } 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';

export function getAuth(event: H3Event): SignedInAuthObject | SignedOutAuthObject {
/**
* @deprecated Use `event.context.auth()` instead.
*/
export function getAuth(event: H3Event): SessionAuthObject {
Copy link
Member Author

@wobsoriano wobsoriano Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're deprecating this helper in favor of event.context.auth() to follow Nuxt conventions where event.context is the standard pattern for accessing request-scoped data.

This function is not documented as well so it's safe to deprecated. We already use event.context.auth() in our docs

deprecated('getAuth', 'Use `event.context.auth()` instead.');

const authObject = event.context.auth();

if (!authObject) {
Expand Down
1 change: 1 addition & 0 deletions packages/nuxt/src/runtime/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
45 changes: 45 additions & 0 deletions packages/nuxt/src/runtime/server/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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'] };

/**
* @internal This type is used to define the `auth` function in the event context.
*/
export interface AuthFn {
/**
* @example
* const auth = event.context.auth({ acceptsToken: ['session_token', 'api_key'] })
*/
<T extends TokenType[]>(
options: AuthOptions & { acceptsToken: T },
):
| InferAuthObjectFromTokenArray<T, SessionAuthObject, MachineAuthObject<Exclude<T[number], SessionTokenType>>>
| InvalidTokenAuthObject;

/**
* @example
* const auth = event.context.auth({ acceptsToken: 'session_token' })
*/
<T extends TokenType>(
options: AuthOptions & { acceptsToken: T },
): InferAuthObjectFromToken<T, SessionAuthObject, MachineAuthObject<Exclude<T, SessionTokenType>>>;

/**
* @example
* const auth = event.context.auth({ acceptsToken: 'any' })
*/
(options: AuthOptions & { acceptsToken: 'any' }): AuthObject;

/**
* @example
* const auth = event.context.auth()
*/
(): SessionAuthObject;
}
4 changes: 2 additions & 2 deletions packages/nuxt/src/runtime/server/utils.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
}
Loading