Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 470
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
bdc16ac2eede94d35e5605a6fa5f992e756fb59ccfb5e366dae61211dcb29521709c63eac3bdfFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
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 }); | ||
MemberAuthor 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 { | ||
| ||
| 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; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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