Skip to content

Commit 348021d

Browse files
authored
chore(backend): Add helper type for auth helpers without request parameters (#6910)
1 parent 0efe78d commit 348021d

File tree

14 files changed

+193
-226
lines changed

14 files changed

+193
-226
lines changed

.changeset/real-trainers-knock.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@clerk/astro": patch
3+
"@clerk/backend": patch
4+
"@clerk/express": patch
5+
"@clerk/fastify": patch
6+
"@clerk/nextjs": patch
7+
"@clerk/nuxt": patch
8+
"@clerk/tanstack-react-start": patch
9+
---
10+
11+
Added internal helper type for `auth` and `getAuth()` functions that don't require a request or context parameter

.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
208208
"nextjs/create-sync-get-auth.mdx",
209209
"nextjs/current-user.mdx",
210210
"nextjs/get-auth.mdx",
211+
"nextjs/session-auth-with-redirect.mdx",
211212
"clerk-react/api-keys.mdx",
212213
"clerk-react/checkout-button-props.mdx",
213214
"clerk-react/checkout-button.mdx",
@@ -247,6 +248,7 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
247248
"backend/email-address.mdx",
248249
"backend/external-account.mdx",
249250
"backend/feature.mdx",
251+
"backend/get-auth-fn-no-request.mdx",
250252
"backend/get-auth-fn.mdx",
251253
"backend/identification-link.mdx",
252254
"backend/infer-auth-object-from-token-array.mdx",

packages/astro/src/server/clerk-middleware.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import type { AuthObject, ClerkClient } from '@clerk/backend';
22
import type {
33
AuthenticateRequestOptions,
4+
AuthOptions,
45
ClerkRequest,
56
RedirectFun,
67
RequestState,
7-
SignedInAuthObject,
8-
SignedOutAuthObject,
98
} from '@clerk/backend/internal';
109
import {
1110
AuthStatus,
@@ -36,18 +35,14 @@ import type {
3635
AstroMiddlewareNextParam,
3736
AstroMiddlewareReturn,
3837
AuthFn,
39-
AuthOptions,
38+
SessionAuthObjectWithRedirect,
4039
} from './types';
4140
import { isRedirect, setHeader } from './utils';
4241

4342
const CONTROL_FLOW_ERROR = {
4443
REDIRECT_TO_SIGN_IN: 'CLERK_PROTECT_REDIRECT_TO_SIGN_IN',
4544
};
4645

47-
type ClerkMiddlewareAuthObject = (SignedInAuthObject | SignedOutAuthObject) & {
48-
redirectToSignIn: (opts?: { returnBackUrl?: URL | string | null }) => Response;
49-
};
50-
5146
type ClerkAstroMiddlewareHandler = (
5247
auth: AuthFn,
5348
context: AstroMiddlewareContextParam,
@@ -396,7 +391,7 @@ const redirectAdapter = (url: string | URL) => {
396391

397392
const createMiddlewareRedirectToSignIn = (
398393
clerkRequest: ClerkRequest,
399-
): ClerkMiddlewareAuthObject['redirectToSignIn'] => {
394+
): SessionAuthObjectWithRedirect['redirectToSignIn'] => {
400395
return (opts = {}) => {
401396
const err = new Error(CONTROL_FLOW_ERROR.REDIRECT_TO_SIGN_IN) as any;
402397
err.returnBackUrl = opts.returnBackUrl === null ? '' : opts.returnBackUrl || clerkRequest.clerkUrl.toString();

packages/astro/src/server/types.ts

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
import type { AuthObject, InvalidTokenAuthObject, MachineAuthObject, SessionAuthObject } from '@clerk/backend';
2-
import type {
3-
AuthenticateRequestOptions,
4-
InferAuthObjectFromToken,
5-
InferAuthObjectFromTokenArray,
6-
RedirectFun,
7-
SessionTokenType,
8-
TokenType,
9-
} from '@clerk/backend/internal';
10-
import type { PendingSessionOptions } from '@clerk/types';
1+
import type { SessionAuthObject } from '@clerk/backend';
2+
import type { GetAuthFnNoRequest, RedirectFun } from '@clerk/backend/internal';
113
import type { APIContext } from 'astro';
124

135
/**
@@ -26,44 +18,8 @@ export type AstroMiddlewareContextParam = APIContext;
2618
export type AstroMiddlewareNextParam = MiddlewareNext;
2719
export type AstroMiddlewareReturn = Response | Promise<Response>;
2820

29-
export type AuthOptions = PendingSessionOptions & Pick<AuthenticateRequestOptions, 'acceptsToken'>;
30-
31-
type SessionAuthObjectWithRedirect = SessionAuthObject & {
21+
export type SessionAuthObjectWithRedirect = SessionAuthObject & {
3222
redirectToSignIn: RedirectFun<Response>;
3323
};
3424

35-
export interface AuthFn {
36-
/**
37-
* @example
38-
* const auth = context.locals.auth({ acceptsToken: ['session_token', 'api_key'] })
39-
*/
40-
<T extends TokenType[]>(
41-
options: AuthOptions & { acceptsToken: T },
42-
):
43-
| InferAuthObjectFromTokenArray<
44-
T,
45-
SessionAuthObjectWithRedirect,
46-
MachineAuthObject<Exclude<T[number], SessionTokenType>>
47-
>
48-
| InvalidTokenAuthObject;
49-
50-
/**
51-
* @example
52-
* const auth = context.locals.auth({ acceptsToken: 'session_token' })
53-
*/
54-
<T extends TokenType>(
55-
options: AuthOptions & { acceptsToken: T },
56-
): InferAuthObjectFromToken<T, SessionAuthObjectWithRedirect, MachineAuthObject<Exclude<T, SessionTokenType>>>;
57-
58-
/**
59-
* @example
60-
* const auth = context.locals.auth({ acceptsToken: 'any' })
61-
*/
62-
(options: AuthOptions & { acceptsToken: 'any' }): AuthObject;
63-
64-
/**
65-
* @example
66-
* const auth = context.locals.auth()
67-
*/
68-
(options?: PendingSessionOptions): SessionAuthObjectWithRedirect;
69-
}
25+
export type AuthFn = GetAuthFnNoRequest<SessionAuthObjectWithRedirect>;

packages/backend/src/internal.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export type {
1313
InferAuthObjectFromToken,
1414
InferAuthObjectFromTokenArray,
1515
GetAuthFn,
16+
AuthOptions,
17+
GetAuthFnNoRequest,
1618
} from './tokens/types';
1719

1820
export { TokenType } from './tokens/tokenTypes';
Lines changed: 91 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,99 @@
11
import { expectTypeOf, test } from 'vitest';
22

3+
import type { RedirectFun } from '../../createRedirect';
34
import type { AuthObject, InvalidTokenAuthObject } from '../authObjects';
4-
import type { GetAuthFn, MachineAuthObject, SessionAuthObject } from '../types';
5-
6-
// Across our SDKs, we have a getAuth() function
7-
const getAuth: GetAuthFn<Request> = (_request: any, _options: any) => {
8-
return {} as any;
9-
};
10-
11-
test('infers the correct AuthObject type for each accepted token type', () => {
12-
const request = new Request('https://example.com');
13-
14-
// Session token by default
15-
expectTypeOf(getAuth(request)).toMatchTypeOf<SessionAuthObject>();
16-
17-
// Individual token types
18-
expectTypeOf(getAuth(request, { acceptsToken: 'session_token' })).toMatchTypeOf<SessionAuthObject>();
19-
expectTypeOf(getAuth(request, { acceptsToken: 'api_key' })).toMatchTypeOf<MachineAuthObject<'api_key'>>();
20-
expectTypeOf(getAuth(request, { acceptsToken: 'm2m_token' })).toMatchTypeOf<MachineAuthObject<'m2m_token'>>();
21-
expectTypeOf(getAuth(request, { acceptsToken: 'oauth_token' })).toMatchTypeOf<MachineAuthObject<'oauth_token'>>();
22-
23-
// Array of token types
24-
expectTypeOf(getAuth(request, { acceptsToken: ['session_token', 'm2m_token'] })).toMatchTypeOf<
25-
SessionAuthObject | MachineAuthObject<'m2m_token'> | InvalidTokenAuthObject
26-
>();
27-
expectTypeOf(getAuth(request, { acceptsToken: ['m2m_token', 'oauth_token'] })).toMatchTypeOf<
28-
MachineAuthObject<'m2m_token' | 'oauth_token'> | InvalidTokenAuthObject
29-
>();
30-
31-
// Any token type
32-
expectTypeOf(getAuth(request, { acceptsToken: 'any' })).toMatchTypeOf<AuthObject>();
5+
import type { GetAuthFn, GetAuthFnNoRequest, MachineAuthObject, SessionAuthObject } from '../types';
6+
7+
describe('getAuth() or auth() with request parameter', () => {
8+
const getAuth: GetAuthFn<Request> = (_request: any, _options: any) => {
9+
return {} as any;
10+
};
11+
12+
test('infers the correct AuthObject type for each accepted token type', () => {
13+
const request = new Request('https://example.com');
14+
15+
// Session token by default
16+
expectTypeOf(getAuth(request)).toExtend<SessionAuthObject>();
17+
18+
// Individual token types
19+
expectTypeOf(getAuth(request, { acceptsToken: 'session_token' })).toExtend<SessionAuthObject>();
20+
expectTypeOf(getAuth(request, { acceptsToken: 'api_key' })).toExtend<MachineAuthObject<'api_key'>>();
21+
expectTypeOf(getAuth(request, { acceptsToken: 'm2m_token' })).toExtend<MachineAuthObject<'m2m_token'>>();
22+
expectTypeOf(getAuth(request, { acceptsToken: 'oauth_token' })).toExtend<MachineAuthObject<'oauth_token'>>();
23+
24+
// Array of token types
25+
expectTypeOf(getAuth(request, { acceptsToken: ['session_token', 'm2m_token'] })).toExtend<
26+
SessionAuthObject | MachineAuthObject<'m2m_token'> | InvalidTokenAuthObject
27+
>();
28+
expectTypeOf(getAuth(request, { acceptsToken: ['m2m_token', 'oauth_token'] })).toExtend<
29+
MachineAuthObject<'m2m_token' | 'oauth_token'> | InvalidTokenAuthObject
30+
>();
31+
32+
// Any token type
33+
expectTypeOf(getAuth(request, { acceptsToken: 'any' })).toExtend<AuthObject>();
34+
});
35+
36+
test('verifies discriminated union works correctly with acceptsToken: any', () => {
37+
const request = new Request('https://example.com');
38+
39+
const auth = getAuth(request, { acceptsToken: 'any' });
40+
41+
if (auth.tokenType === 'session_token') {
42+
expectTypeOf(auth).toExtend<SessionAuthObject>();
43+
} else if (auth.tokenType === 'api_key') {
44+
expectTypeOf(auth).toExtend<MachineAuthObject<'api_key'>>();
45+
} else if (auth.tokenType === 'm2m_token') {
46+
expectTypeOf(auth).toExtend<MachineAuthObject<'m2m_token'>>();
47+
} else if (auth.tokenType === 'oauth_token') {
48+
expectTypeOf(auth).toExtend<MachineAuthObject<'oauth_token'>>();
49+
}
50+
});
3351
});
3452

35-
test('verifies discriminated union works correctly with acceptsToken: any', () => {
36-
const request = new Request('https://example.com');
53+
describe('getAuth() or auth() without request parameter', () => {
54+
type SessionAuthWithRedirect = SessionAuthObject & {
55+
redirectToSignIn: RedirectFun<Response>;
56+
redirectToSignUp: RedirectFun<Response>;
57+
};
58+
59+
// Mimic Next.js auth() helper
60+
const auth: GetAuthFnNoRequest<SessionAuthWithRedirect, true> = (_options: any) => {
61+
return {} as any;
62+
};
63+
64+
test('infers the correct AuthObject type for each accepted token type', async () => {
65+
// Session token by default
66+
expectTypeOf(await auth()).toExtend<SessionAuthWithRedirect>();
67+
68+
// Individual token types
69+
expectTypeOf(await auth({ acceptsToken: 'session_token' })).toExtend<SessionAuthWithRedirect>();
70+
expectTypeOf(await auth({ acceptsToken: 'api_key' })).toExtend<MachineAuthObject<'api_key'>>();
71+
expectTypeOf(await auth({ acceptsToken: 'm2m_token' })).toExtend<MachineAuthObject<'m2m_token'>>();
72+
expectTypeOf(await auth({ acceptsToken: 'oauth_token' })).toExtend<MachineAuthObject<'oauth_token'>>();
73+
74+
// Array of token types
75+
expectTypeOf(await auth({ acceptsToken: ['session_token', 'm2m_token'] })).toExtend<
76+
SessionAuthWithRedirect | MachineAuthObject<'m2m_token'> | InvalidTokenAuthObject
77+
>();
78+
expectTypeOf(await auth({ acceptsToken: ['m2m_token', 'oauth_token'] })).toExtend<
79+
MachineAuthObject<'m2m_token' | 'oauth_token'> | InvalidTokenAuthObject
80+
>();
81+
82+
// Any token type
83+
expectTypeOf(await auth({ acceptsToken: 'any' })).toExtend<AuthObject>();
84+
});
3785

38-
const auth = getAuth(request, { acceptsToken: 'any' });
86+
test('verifies discriminated union works correctly with acceptsToken: any', async () => {
87+
const authObject = await auth({ acceptsToken: 'any' });
3988

40-
if (auth.tokenType === 'session_token') {
41-
expectTypeOf(auth).toMatchTypeOf<SessionAuthObject>();
42-
} else if (auth.tokenType === 'api_key') {
43-
expectTypeOf(auth).toMatchTypeOf<MachineAuthObject<'api_key'>>();
44-
} else if (auth.tokenType === 'm2m_token') {
45-
expectTypeOf(auth).toMatchTypeOf<MachineAuthObject<'m2m_token'>>();
46-
} else if (auth.tokenType === 'oauth_token') {
47-
expectTypeOf(auth).toMatchTypeOf<MachineAuthObject<'oauth_token'>>();
48-
}
89+
if (authObject.tokenType === 'session_token') {
90+
expectTypeOf(authObject).toExtend<SessionAuthWithRedirect>();
91+
} else if (authObject.tokenType === 'api_key') {
92+
expectTypeOf(authObject).toExtend<MachineAuthObject<'api_key'>>();
93+
} else if (authObject.tokenType === 'm2m_token') {
94+
expectTypeOf(authObject).toExtend<MachineAuthObject<'m2m_token'>>();
95+
} else if (authObject.tokenType === 'oauth_token') {
96+
expectTypeOf(authObject).toExtend<MachineAuthObject<'oauth_token'>>();
97+
}
98+
});
4999
});

packages/backend/src/tokens/types.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,13 @@ export type MachineAuthObject<T extends Exclude<TokenType, SessionTokenType>> =
187187
? AuthenticatedMachineObject<T> | UnauthenticatedMachineObject<T>
188188
: never;
189189

190-
type AuthOptions = PendingSessionOptions & { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] };
190+
export type AuthOptions = PendingSessionOptions & { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] };
191191

192192
type MaybePromise<T, IsPromise extends boolean> = IsPromise extends true ? Promise<T> : T;
193193

194194
/**
195195
* Shared generic overload type for getAuth() helpers across SDKs.
196196
*
197-
* - Parameterized by the request type (RequestType).
198197
* - Handles different accepted token types and their corresponding return types.
199198
*/
200199
export interface GetAuthFn<RequestType, ReturnsPromise extends boolean = false> {
@@ -235,3 +234,51 @@ export interface GetAuthFn<RequestType, ReturnsPromise extends boolean = false>
235234
*/
236235
(req: RequestType, options?: PendingSessionOptions): MaybePromise<SessionAuthObject, ReturnsPromise>;
237236
}
237+
238+
/**
239+
* Shared generic overload type for auth() or getAuth() helpers that don't require a request parameter.
240+
*
241+
* - Handles different accepted token types and their corresponding return types.
242+
* - The SessionAuthType parameter allows frameworks to extend the base SessionAuthObject with additional properties like redirect methods.
243+
*/
244+
export interface GetAuthFnNoRequest<
245+
SessionAuthType extends SessionAuthObject = SessionAuthObject,
246+
ReturnsPromise extends boolean = false,
247+
> {
248+
/**
249+
* @example
250+
* const authObject = await auth({ acceptsToken: ['session_token', 'api_key'] })
251+
*/
252+
<T extends TokenType[]>(
253+
options: AuthOptions & { acceptsToken: T },
254+
): MaybePromise<
255+
| InferAuthObjectFromTokenArray<T, SessionAuthType, MachineAuthObject<Exclude<T[number], SessionTokenType>>>
256+
| InvalidTokenAuthObject,
257+
ReturnsPromise
258+
>;
259+
260+
/**
261+
* @example
262+
* const authObject = await auth({ acceptsToken: 'session_token' })
263+
*/
264+
<T extends TokenType>(
265+
options: AuthOptions & { acceptsToken: T },
266+
): MaybePromise<
267+
InferAuthObjectFromToken<T, SessionAuthType, MachineAuthObject<Exclude<T, SessionTokenType>>>,
268+
ReturnsPromise
269+
>;
270+
271+
/**
272+
* @example
273+
* const authObject = await auth({ acceptsToken: 'any' })
274+
*/
275+
(
276+
options: AuthOptions & { acceptsToken: 'any' },
277+
): MaybePromise<Exclude<AuthObject, SessionAuthObject> | SessionAuthType, ReturnsPromise>;
278+
279+
/**
280+
* @example
281+
* const authObject = await auth()
282+
*/
283+
(options?: PendingSessionOptions): MaybePromise<SessionAuthType, ReturnsPromise>;
284+
}

packages/express/src/getAuth.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
import type { AuthenticateRequestOptions, GetAuthFn } from '@clerk/backend/internal';
1+
import type { AuthOptions, GetAuthFn } from '@clerk/backend/internal';
22
import { getAuthObjectForAcceptedToken } from '@clerk/backend/internal';
3-
import type { PendingSessionOptions } from '@clerk/types';
43
import type { Request as ExpressRequest } from 'express';
54

65
import { middlewareRequired } from './errors';
76
import { requestHasAuthObject } from './utils';
87

9-
type GetAuthOptions = PendingSessionOptions & { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] };
10-
118
/**
129
* Retrieves the Clerk AuthObject using the current request object.
1310
*
1411
* @param {GetAuthOptions} options - Optional configuration for retriving auth object.
1512
* @returns {AuthObject} Object with information about the request state and claims.
1613
* @throws {Error} `clerkMiddleware` or `requireAuth` is required to be set in the middleware chain before this util is used.
1714
*/
18-
export const getAuth: GetAuthFn<ExpressRequest> = ((req: ExpressRequest, options?: GetAuthOptions) => {
15+
export const getAuth: GetAuthFn<ExpressRequest> = ((req: ExpressRequest, options?: AuthOptions) => {
1916
if (!requestHasAuthObject(req)) {
2017
throw new Error(middlewareRequired('getAuth'));
2118
}

packages/fastify/src/getAuth.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
import type {
2-
AuthenticateRequestOptions,
3-
GetAuthFn,
4-
SignedInAuthObject,
5-
SignedOutAuthObject,
6-
} from '@clerk/backend/internal';
1+
import type { AuthOptions, GetAuthFn, SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend/internal';
72
import { getAuthObjectForAcceptedToken } from '@clerk/backend/internal';
83
import type { FastifyRequest } from 'fastify';
94

105
import { pluginRegistrationRequired } from './errors';
116

12-
type GetAuthOptions = { acceptsToken?: AuthenticateRequestOptions['acceptsToken'] };
13-
14-
export const getAuth: GetAuthFn<FastifyRequest> = ((req: FastifyRequest, options?: GetAuthOptions) => {
7+
export const getAuth: GetAuthFn<FastifyRequest> = ((req: FastifyRequest, options?: AuthOptions) => {
158
const authReq = req as FastifyRequest & { auth: SignedInAuthObject | SignedOutAuthObject };
169

1710
if (!authReq.auth) {

0 commit comments

Comments
 (0)