Skip to content

Commit a8aa04a

Browse files
authored
chore(backend,nextjs): Introduce treatPendingAsSignedOut option to server-side utilities and control components (#5756)
1 parent ced8912 commit a8aa04a

File tree

6 files changed

+59
-18
lines changed

6 files changed

+59
-18
lines changed

.changeset/tender-brooms-look.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@clerk/nextjs': minor
3+
---
4+
5+
Introduce `treatPendingAsSignedOut` option to `getAuth` and `auth` from `clerkMiddleware`
6+
7+
By default, `treatPendingAsSignedOut` is set to `true`, which means pending sessions are treated as signed-out. You can set this option to `false` to treat pending sessions as authenticated.
8+
9+
```ts
10+
const { userId } = auth({ treatPendingAsSignedOut: false })
11+
```
12+
13+
```ts
14+
const { userId } = getAuth(req, { treatPendingAsSignedOut: false })
15+
```
16+
17+
```tsx
18+
<SignedIn treatPendingAsSignedOut={false}>
19+
User has a session that is either pending (requires tasks resolution) or active
20+
</SignedIn>
21+
```

packages/nextjs/src/app-router/server/auth.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { AuthObject } from '@clerk/backend';
22
import { constants, createClerkRequest, createRedirect, type RedirectFun } from '@clerk/backend/internal';
3+
import type { PendingSessionOptions } from '@clerk/types';
34
import { notFound, redirect } from 'next/navigation';
45

56
import { PUBLISHABLE_KEY, SIGN_IN_URL, SIGN_UP_URL } from '../../server/constants';
@@ -38,7 +39,7 @@ type Auth = AuthObject & {
3839
};
3940

4041
export interface AuthFn {
41-
(): Promise<Auth>;
42+
(options?: PendingSessionOptions): Promise<Auth>;
4243

4344
/**
4445
* `auth` includes a single property, the `protect()` method, which you can use in two ways:
@@ -68,7 +69,7 @@ export interface AuthFn {
6869
* - Only works on the server-side, such as in Server Components, Route Handlers, and Server Actions.
6970
* - Requires [`clerkMiddleware()`](https://clerk.com/docs/references/nextjs/clerk-middleware) to be configured.
7071
*/
71-
export const auth: AuthFn = async () => {
72+
export const auth: AuthFn = async ({ treatPendingAsSignedOut } = {}) => {
7273
// eslint-disable-next-line @typescript-eslint/no-require-imports
7374
require('server-only');
7475

@@ -89,7 +90,7 @@ export const auth: AuthFn = async () => {
8990
const authObject = await createAsyncGetAuth({
9091
debugLoggerName: 'auth()',
9192
noAuthStatusMessage: authAuthHeaderMissing('auth', await stepsBasedOnSrcDirectory()),
92-
})(request);
93+
})(request, { treatPendingAsSignedOut });
9394

9495
const clerkUrl = getAuthKeyFromRequest(request, 'ClerkUrl');
9596

packages/nextjs/src/app-router/server/controlComponents.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import type { ProtectProps } from '@clerk/clerk-react';
2+
import type { PendingSessionOptions } from '@clerk/types';
23
import React from 'react';
34

45
import { auth } from './auth';
56

6-
export async function SignedIn(props: React.PropsWithChildren): Promise<React.JSX.Element | null> {
7+
export async function SignedIn(
8+
props: React.PropsWithChildren<PendingSessionOptions>,
9+
): Promise<React.JSX.Element | null> {
710
const { children } = props;
8-
const { userId } = await auth();
11+
const { userId } = await auth({ treatPendingAsSignedOut: props.treatPendingAsSignedOut });
912
return userId ? <>{children}</> : null;
1013
}
1114

12-
export async function SignedOut(props: React.PropsWithChildren): Promise<React.JSX.Element | null> {
15+
export async function SignedOut(
16+
props: React.PropsWithChildren<PendingSessionOptions>,
17+
): Promise<React.JSX.Element | null> {
1318
const { children } = props;
14-
const { userId } = await auth();
19+
const { userId } = await auth({ treatPendingAsSignedOut: props.treatPendingAsSignedOut });
1520
return userId ? null : <>{children}</>;
1621
}
1722

@@ -29,7 +34,7 @@ export async function SignedOut(props: React.PropsWithChildren): Promise<React.J
2934
*/
3035
export async function Protect(props: ProtectProps): Promise<React.JSX.Element | null> {
3136
const { children, fallback, ...restAuthorizedParams } = props;
32-
const { has, userId } = await auth();
37+
const { has, userId } = await auth({ treatPendingAsSignedOut: props.treatPendingAsSignedOut });
3338

3439
/**
3540
* Fallback to UI provided by user or `null` if authorization checks failed

packages/nextjs/src/server/clerkMiddleware.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { AuthObject, ClerkClient } from '@clerk/backend';
22
import type { AuthenticateRequestOptions, ClerkRequest, RedirectFun, RequestState } from '@clerk/backend/internal';
33
import { AuthStatus, constants, createClerkRequest, createRedirect } from '@clerk/backend/internal';
44
import { parsePublishableKey } from '@clerk/shared/keys';
5+
import type { PendingSessionOptions } from '@clerk/types';
56
import { notFound as nextjsNotFound } from 'next/navigation';
67
import type { NextMiddleware, NextRequest } from 'next/server';
78
import { NextResponse } from 'next/server';
@@ -41,7 +42,7 @@ export type ClerkMiddlewareAuthObject = AuthObject & {
4142
};
4243

4344
export interface ClerkMiddlewareAuth {
44-
(): Promise<ClerkMiddlewareAuthObject>;
45+
(opts?: PendingSessionOptions): Promise<ClerkMiddlewareAuthObject>;
4546

4647
protect: AuthProtect;
4748
}
@@ -182,11 +183,14 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl
182183
const redirectToSignUp = createMiddlewareRedirectToSignUp(clerkRequest);
183184
const protect = await createMiddlewareProtect(clerkRequest, authObject, redirectToSignIn);
184185

185-
const authObjWithMethods: ClerkMiddlewareAuthObject = Object.assign(authObject, {
186-
redirectToSignIn,
187-
redirectToSignUp,
188-
});
189-
const authHandler = () => Promise.resolve(authObjWithMethods);
186+
const authHandler = (opts?: PendingSessionOptions) => {
187+
const authObjWithMethods: ClerkMiddlewareAuthObject = Object.assign(requestState.toAuth(opts), {
188+
redirectToSignIn,
189+
redirectToSignUp,
190+
});
191+
192+
return Promise.resolve(authObjWithMethods);
193+
};
190194
authHandler.protect = protect;
191195

192196
let handlerResult: Response = NextResponse.next();

packages/nextjs/src/server/createGetAuth.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AuthObject } from '@clerk/backend';
22
import { constants } from '@clerk/backend/internal';
33
import { isTruthy } from '@clerk/shared/underscore';
4+
import type { PendingSessionOptions } from '@clerk/types';
45

56
import { withLogger } from '../utils/debugLogger';
67
import { isNextWithUnstableServerActions } from '../utils/sdk-versions';
@@ -22,7 +23,7 @@ export const createAsyncGetAuth = ({
2223
noAuthStatusMessage: string;
2324
}) =>
2425
withLogger(debugLoggerName, logger => {
25-
return async (req: RequestLike, opts?: { secretKey?: string }): Promise<AuthObject> => {
26+
return async (req: RequestLike, opts?: { secretKey?: string } & PendingSessionOptions): Promise<AuthObject> => {
2627
if (isTruthy(getHeader(req, constants.Headers.EnableDebug))) {
2728
logger.enable();
2829
}
@@ -52,7 +53,7 @@ export const createAsyncGetAuth = ({
5253
/**
5354
* Previous known as `createGetAuth`. We needed to create a sync and async variant in order to allow for improvements
5455
* that required dynamic imports (using `require` would not work).
55-
* It powers the synchronous top-level api `getAuh()`.
56+
* It powers the synchronous top-level api `getAuth()`.
5657
*/
5758
export const createSyncGetAuth = ({
5859
debugLoggerName,
@@ -62,7 +63,7 @@ export const createSyncGetAuth = ({
6263
noAuthStatusMessage: string;
6364
}) =>
6465
withLogger(debugLoggerName, logger => {
65-
return (req: RequestLike, opts?: { secretKey?: string }): AuthObject => {
66+
return (req: RequestLike, opts?: { secretKey?: string } & PendingSessionOptions): AuthObject => {
6667
if (isTruthy(getHeader(req, constants.Headers.EnableDebug))) {
6768
logger.enable();
6869
}

packages/nextjs/src/server/data/getAuthDataFromRequest.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AuthObject } from '@clerk/backend';
22
import { AuthStatus, constants, signedInAuthObject, signedOutAuthObject } from '@clerk/backend/internal';
33
import { decodeJwt } from '@clerk/backend/jwt';
4+
import type { PendingSessionOptions } from '@clerk/types';
45

56
import type { LoggerNoCommit } from '../../utils/debugLogger';
67
import { API_URL, API_VERSION, PUBLISHABLE_KEY, SECRET_KEY } from '../constants';
@@ -14,7 +15,10 @@ import { assertTokenSignature, decryptClerkRequestData } from '../utils';
1415
*/
1516
export function getAuthDataFromRequest(
1617
req: RequestLike,
17-
opts: { secretKey?: string; logger?: LoggerNoCommit } = {},
18+
{
19+
treatPendingAsSignedOut = true,
20+
...opts
21+
}: { secretKey?: string; logger?: LoggerNoCommit } & PendingSessionOptions = {},
1822
): AuthObject {
1923
const authStatus = getAuthKeyFromRequest(req, 'AuthStatus');
2024
const authToken = getAuthKeyFromRequest(req, 'AuthToken');
@@ -35,6 +39,7 @@ export function getAuthDataFromRequest(
3539
authStatus,
3640
authMessage,
3741
authReason,
42+
treatPendingAsSignedOut,
3843
};
3944

4045
opts.logger?.debug('auth options', options);
@@ -53,5 +58,9 @@ export function getAuthDataFromRequest(
5358
authObject = signedInAuthObject(options, jwt.raw.text, jwt.payload);
5459
}
5560

61+
if (treatPendingAsSignedOut && authObject.sessionStatus === 'pending') {
62+
authObject = signedOutAuthObject(options, authObject.sessionStatus);
63+
}
64+
5665
return authObject;
5766
}

0 commit comments

Comments
 (0)