Skip to content

Commit

Permalink
feat(nextjs): Introduce auth().protect() for App Router
Browse files Browse the repository at this point in the history
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
  • Loading branch information
panteliselef committed Dec 3, 2023
1 parent 39c65e2 commit bda5428
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 7 deletions.
74 changes: 72 additions & 2 deletions packages/nextjs/src/app-router/server/auth.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,82 @@
import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend';
import type {
CheckAuthorizationWithCustomPermissions,
OrganizationCustomPermission,
OrganizationCustomRole,
} from '@clerk/types';
import { notFound } from 'next/navigation';

import { authAuthHeaderMissing } from '../../server/errors';
import { buildClerkProps, createGetAuth } from '../../server/getAuth';
import type { AuthObjectWithoutResources } from '../../server/types';
import { buildRequestLike } from './utils';

export const auth = () => {
return createGetAuth({
const authObject = createGetAuth({
debugLoggerName: 'auth()',
noAuthStatusMessage: authAuthHeaderMissing(),
})(buildRequestLike());
})(buildRequestLike()) as
| AuthObjectWithoutResources<
SignedInAuthObject & {
protect: (
params?:
| {
role: OrganizationCustomRole;
permission?: never;
}
| {
role?: never;
permission: OrganizationCustomPermission;
}
| ((has: CheckAuthorizationWithCustomPermissions) => boolean),
) => AuthObjectWithoutResources<SignedInAuthObject>;
}
>
/**
* Add a comment
*/
| AuthObjectWithoutResources<
SignedOutAuthObject & {
protect: never;
}
>;

authObject.protect = params => {
/**
* User is not authenticated
*/
if (!authObject.userId) {
notFound();
}

/**
* User is authenticated
*/
if (!params) {
return { ...authObject };
}

/**
* if a function is passed and returns false then throw not found
*/
if (typeof params === 'function') {
if (params(authObject.has)) {
return { ...authObject };
}
return notFound();
}

/**
* Checking if user is authorized when permission or role is passed
*/
if (authObject.has(params)) {
return { ...authObject };
}

notFound();
};

return authObject;
};

export const initialState = () => {
Expand Down
6 changes: 2 additions & 4 deletions packages/nextjs/src/server/getAuth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AuthObject, Organization, Session, SignedInAuthObject, SignedOutAuthObject, User } from '@clerk/backend';
import type { Organization, Session, SignedInAuthObject, SignedOutAuthObject, User } from '@clerk/backend';
import {
AuthStatus,
constants,
Expand All @@ -12,11 +12,9 @@ import {
import { withLogger } from '../utils/debugLogger';
import { API_URL, API_VERSION, SECRET_KEY } from './constants';
import { getAuthAuthHeaderMissing } from './errors';
import type { RequestLike } from './types';
import type { AuthObjectWithoutResources, RequestLike } from './types';
import { getAuthKeyFromRequest, getCookie, getHeader, injectSSRStateIntoObject } from './utils';

type AuthObjectWithoutResources<T extends AuthObject> = Omit<T, 'user' | 'organization' | 'session'>;

export const createGetAuth = ({
debugLoggerName,
noAuthStatusMessage,
Expand Down
4 changes: 3 additions & 1 deletion packages/nextjs/src/server/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { OptionalVerifyTokenOptions } from '@clerk/backend';
import type { AuthObject, OptionalVerifyTokenOptions } from '@clerk/backend';
import type { MultiDomainAndOrProxy } from '@clerk/types';
import type { IncomingMessage } from 'http';
import type { NextApiRequest } from 'next';
Expand All @@ -20,3 +20,5 @@ export type WithAuthOptions = OptionalVerifyTokenOptions &
};

export type NextMiddlewareResult = Awaited<ReturnType<NextMiddleware>>;

export type AuthObjectWithoutResources<T extends AuthObject> = Omit<T, 'user' | 'organization' | 'session'>;

0 comments on commit bda5428

Please sign in to comment.