-
Notifications
You must be signed in to change notification settings - Fork 419
feat(nuxt): Introduce machine auth #6391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bdc16ac
2eede94
d35e560
5a6fa5f
992e756
fb59ccf
b5e366d
ae61211
dcb2952
1709c63
eac3bdf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| --- | ||
| '@clerk/nuxt': minor | ||
| --- | ||
|
|
||
| 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 {} | ||
| }) | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<SessionAuthObject>(); | ||
|
|
||
| // Individual token types | ||
| expectTypeOf(event.locals.auth({ acceptsToken: 'session_token' })).toMatchTypeOf<SessionAuthObject>(); | ||
| expectTypeOf(event.locals.auth({ acceptsToken: 'api_key' })).toMatchTypeOf<MachineAuthObject<'api_key'>>(); | ||
| expectTypeOf(event.locals.auth({ acceptsToken: 'machine_token' })).toMatchTypeOf< | ||
| MachineAuthObject<'machine_token'> | ||
| >(); | ||
| expectTypeOf(event.locals.auth({ acceptsToken: 'oauth_token' })).toMatchTypeOf<MachineAuthObject<'oauth_token'>>(); | ||
|
|
||
| // 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<AuthObject>(); | ||
| }); | ||
|
|
||
| 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<SessionAuthObject>(); | ||
| } else if (auth.tokenType === 'api_key') { | ||
| expectTypeOf(auth).toMatchTypeOf<MachineAuthObject<'api_key'>>(); | ||
| } else if (auth.tokenType === 'machine_token') { | ||
| expectTypeOf(auth).toMatchTypeOf<MachineAuthObject<'machine_token'>>(); | ||
| } else if (auth.tokenType === 'oauth_token') { | ||
| expectTypeOf(auth).toMatchTypeOf<MachineAuthObject<'oauth_token'>>(); | ||
| } | ||
| }); |
| 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[]) { | ||
|
|
@@ -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,13 +109,18 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { | |
| } | ||
|
|
||
| const authObject = requestState.toAuth(); | ||
| const authHandler = () => authObject; | ||
| const authHandler: AuthFn = ((options?: AuthOptions) => { | ||
| const acceptsToken = options?.acceptsToken ?? TokenType.SessionToken; | ||
| return getAuthObjectForAcceptedToken({ authObject, acceptsToken }); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| }) 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]; | ||
| }, | ||
| }); | ||
|
|
||
|
|
||
| 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 { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're deprecating this helper in favor of This function is not documented as well so it's safe to deprecated. We already use |
||
| deprecated('getAuth', 'Use `event.context.auth()` instead.'); | ||
|
|
||
| const authObject = event.context.auth(); | ||
|
|
||
| if (!authObject) { | ||
|
|
||
| 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; | ||
| } |
There was a problem hiding this comment.
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