Skip to content

Commit bda5428

Browse files
committed
feat(nextjs): Introduce auth().protect() for App Router
Allow per page protection in app router. This utility will automatically throw a 404 error if user is not authorized or authenticated. When `auth().protect()` is called - inside a page or layout file it will render the nearest `not-found` component set by the developer - inside a route handler it will return empty response body with a 404 status code
1 parent 39c65e2 commit bda5428

File tree

3 files changed

+77
-7
lines changed

3 files changed

+77
-7
lines changed

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

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,82 @@
1+
import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend';
2+
import type {
3+
CheckAuthorizationWithCustomPermissions,
4+
OrganizationCustomPermission,
5+
OrganizationCustomRole,
6+
} from '@clerk/types';
7+
import { notFound } from 'next/navigation';
8+
19
import { authAuthHeaderMissing } from '../../server/errors';
210
import { buildClerkProps, createGetAuth } from '../../server/getAuth';
11+
import type { AuthObjectWithoutResources } from '../../server/types';
312
import { buildRequestLike } from './utils';
413

514
export const auth = () => {
6-
return createGetAuth({
15+
const authObject = createGetAuth({
716
debugLoggerName: 'auth()',
817
noAuthStatusMessage: authAuthHeaderMissing(),
9-
})(buildRequestLike());
18+
})(buildRequestLike()) as
19+
| AuthObjectWithoutResources<
20+
SignedInAuthObject & {
21+
protect: (
22+
params?:
23+
| {
24+
role: OrganizationCustomRole;
25+
permission?: never;
26+
}
27+
| {
28+
role?: never;
29+
permission: OrganizationCustomPermission;
30+
}
31+
| ((has: CheckAuthorizationWithCustomPermissions) => boolean),
32+
) => AuthObjectWithoutResources<SignedInAuthObject>;
33+
}
34+
>
35+
/**
36+
* Add a comment
37+
*/
38+
| AuthObjectWithoutResources<
39+
SignedOutAuthObject & {
40+
protect: never;
41+
}
42+
>;
43+
44+
authObject.protect = params => {
45+
/**
46+
* User is not authenticated
47+
*/
48+
if (!authObject.userId) {
49+
notFound();
50+
}
51+
52+
/**
53+
* User is authenticated
54+
*/
55+
if (!params) {
56+
return { ...authObject };
57+
}
58+
59+
/**
60+
* if a function is passed and returns false then throw not found
61+
*/
62+
if (typeof params === 'function') {
63+
if (params(authObject.has)) {
64+
return { ...authObject };
65+
}
66+
return notFound();
67+
}
68+
69+
/**
70+
* Checking if user is authorized when permission or role is passed
71+
*/
72+
if (authObject.has(params)) {
73+
return { ...authObject };
74+
}
75+
76+
notFound();
77+
};
78+
79+
return authObject;
1080
};
1181

1282
export const initialState = () => {

packages/nextjs/src/server/getAuth.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AuthObject, Organization, Session, SignedInAuthObject, SignedOutAuthObject, User } from '@clerk/backend';
1+
import type { Organization, Session, SignedInAuthObject, SignedOutAuthObject, User } from '@clerk/backend';
22
import {
33
AuthStatus,
44
constants,
@@ -12,11 +12,9 @@ import {
1212
import { withLogger } from '../utils/debugLogger';
1313
import { API_URL, API_VERSION, SECRET_KEY } from './constants';
1414
import { getAuthAuthHeaderMissing } from './errors';
15-
import type { RequestLike } from './types';
15+
import type { AuthObjectWithoutResources, RequestLike } from './types';
1616
import { getAuthKeyFromRequest, getCookie, getHeader, injectSSRStateIntoObject } from './utils';
1717

18-
type AuthObjectWithoutResources<T extends AuthObject> = Omit<T, 'user' | 'organization' | 'session'>;
19-
2018
export const createGetAuth = ({
2119
debugLoggerName,
2220
noAuthStatusMessage,

packages/nextjs/src/server/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { OptionalVerifyTokenOptions } from '@clerk/backend';
1+
import type { AuthObject, OptionalVerifyTokenOptions } from '@clerk/backend';
22
import type { MultiDomainAndOrProxy } from '@clerk/types';
33
import type { IncomingMessage } from 'http';
44
import type { NextApiRequest } from 'next';
@@ -20,3 +20,5 @@ export type WithAuthOptions = OptionalVerifyTokenOptions &
2020
};
2121

2222
export type NextMiddlewareResult = Awaited<ReturnType<NextMiddleware>>;
23+
24+
export type AuthObjectWithoutResources<T extends AuthObject> = Omit<T, 'user' | 'organization' | 'session'>;

0 commit comments

Comments
 (0)