Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 469
feat(nextjs): Introduce clerkMiddleware#2404
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
d44453b23a334e2caffea3056a8910d5c49a5ca282832ca35File 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,64 @@ | ||
| --- | ||
| '@clerk/nextjs': patch | ||
| --- | ||
| Introduce the new `clerkMiddleware` helper to allow for more flexibility in how Clerk is integrated into your Next.js middleware. Example usage can be found below, for more details, For more details, please see the [clerkMiddleware](https://clerk.com/docs/references/nextjs/clerkMiddleware) documentation. | ||
| The `clerkMiddleware` helper effectively replaces the older `authMiddleware` helper, which is now considered deprecated and will be removed in the next major release. | ||
| ### 1. Protect a route that requires authentication | ||
| ```js | ||
| import { clerkMiddleware } from '@clerk/nextjs/server'; | ||
| export default clerkMiddleware(auth => { | ||
| const { userId } = auth().protect(); | ||
| // userId is now available for use in your route handler | ||
| // for page requests, calling protect will automatically redirect the user to the sign-in URL if they are not authenticated | ||
| return NextResponse.next(); | ||
| }); | ||
| ``` | ||
| ### 2. Protect a route that requires specific permissions | ||
| ```js | ||
| import { clerkMiddleware } from '@clerk/nextjs/server'; | ||
| export default clerkMiddleware(auth => { | ||
| const { userId } = auth().protect({ permission: 'org:domains:delete'}); | ||
| // userId is now available for use in your route handler | ||
| // for page requests, calling protect will automatically throw a notFound error if the user does not have the required permissions | ||
| return NextResponse.next(); | ||
| }); | ||
| ``` | ||
| ### 2. Manually redirect to sign-in URL using the redirectToSignIn helper | ||
| ```js | ||
| import { clerkMiddleware } from '@clerk/nextjs/server'; | ||
| export default clerkMiddleware(auth => { | ||
| // If you want more fine-grained control, you can always use the low-level redirectToSignIn helper | ||
| if(!auth().userId) { | ||
| return auth().redirectToSignIn(); | ||
| } | ||
| return NextResponse.next(); | ||
| }); | ||
| ``` | ||
| This commit also introduces the experimental `createRouteMatcher` helper, which can be used to create a route matcher that matches a route against the current request. This is useful for creating custom logic based on which routes you want to handle as protected or public. | ||
| ```js | ||
| import { clerkMiddleware, experimental_createRouteMatcher } from '@clerk/nextjs/server'; | ||
| const isProtectedRoute = experimental_createRouteMatcher([/protected.*/]); | ||
| export default clerkMiddleware((auth, request) => { | ||
| if(isProtectedRoute(request)) { | ||
| auth().protect(); | ||
| } | ||
| return NextResponse.next(); | ||
| }); | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,15 @@ | ||
| /* eslint-disable turbo/no-undeclared-env-vars */ | ||
| import { constants } from '../constants'; | ||
| export const parseEnvOptions = () => { | ||
| const appIds = constants.E2E_APP_ID ? constants.E2E_APP_ID.split(',') : []; | ||
| const appUrl = constants.E2E_APP_URL; | ||
| const appPk = constants.E2E_APP_PK; | ||
| const appSk = constants.E2E_APP_SK; | ||
| const clerkApiUrl = constants.E2E_CLERK_API_URL; | ||
| if (appIds.length && appUrl) { | ||
| throw new Error('E2E_APP_ID cannot be used with E2E_APP_URL'); | ||
| } | ||
| return { appIds, appUrl, appPk, appSk }; | ||
| return { appIds, appUrl, appPk, appSk, clerkApiUrl }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| class ClerkUrl extends URL { | ||
| public isCrossOrigin(other: URL | string) { | ||
| return this.origin !== new URL(other.toString()).origin; | ||
| } | ||
| } | ||
| export type WithClerkUrl<T> = T & { | ||
| /** | ||
| * When a NextJs app is hosted on a platform different from Vercel | ||
| * or inside a container (Netlify, Fly.io, AWS Amplify, docker etc), | ||
| * req.url is always set to `localhost:3000` instead of the actual host of the app. | ||
| * | ||
| * The `authMiddleware` uses the value of the available req.headers in order to construct | ||
| * and use the correct url internally. This url is then exposed as `experimental_clerkUrl`, | ||
| * intended to be used within `beforeAuth` and `afterAuth` if needed. | ||
| */ | ||
| clerkUrl: ClerkUrl; | ||
| }; | ||
| export const createClerkUrl = (...args: ConstructorParameters<typeof ClerkUrl>): ClerkUrl => { | ||
| return new ClerkUrl(...args); | ||
| }; | ||
| export type { ClerkUrl }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,20 @@ | ||
| import type { AuthObject } from '@clerk/backend/internal'; | ||
| import { notFound, redirect } from 'next/navigation'; | ||
nikosdouvlis marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| import { authAuthHeaderMissing } from '../../server/errors'; | ||
| import { buildClerkProps, createGetAuth } from '../../server/getAuth'; | ||
| import type { AuthProtect } from './protect'; | ||
| import { createProtect } from './protect'; | ||
| import type { AuthProtect } from '../../server/protect'; | ||
| import { createProtect } from '../../server/protect'; | ||
| import { buildRequestLike } from './utils'; | ||
| export const auth = (): AuthObject & { protect: AuthProtect } => { | ||
| const request = buildRequestLike(); | ||
| const authObject = createGetAuth({ | ||
| debugLoggerName: 'auth()', | ||
| noAuthStatusMessage: authAuthHeaderMissing(), | ||
| })(buildRequestLike()); | ||
| })(request); | ||
| return Object.assign(authObject, { protect: createProtect(authObject) }); | ||
| return Object.assign(authObject, { protect: createProtect({ request, authObject, notFound, redirect }) }); | ||
| }; | ||
| export const initialState = () => { | ||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -31,27 +31,6 @@ describe('ClerkProvider', () => { | ||
| it('domain + isSatellite (satellite app)', () => { | ||
| expectTypeOf({ ...defaultProps, domain: 'test', isSatellite: true }).toMatchTypeOf<ClerkProviderProps>(); | ||
| }); | ||
| it('only domain is not allowed', () => { | ||
| expectTypeOf({ ...defaultProps, domain: 'test' }).not.toMatchTypeOf<ClerkProviderProps>(); | ||
| }); | ||
| it('only isSatellite is not allowed', () => { | ||
| expectTypeOf({ ...defaultProps, isSatellite: true }).not.toMatchTypeOf<ClerkProviderProps>(); | ||
| }); | ||
| it('proxyUrl + domain is not allowed', () => { | ||
| expectTypeOf({ ...defaultProps, proxyUrl: 'test', domain: 'test' }).not.toMatchTypeOf<ClerkProviderProps>(); | ||
| }); | ||
| it('proxyUrl + domain + isSatellite is not allowed', () => { | ||
| expectTypeOf({ | ||
| ...defaultProps, | ||
| proxyUrl: 'test', | ||
| domain: 'test', | ||
| isSatellite: true, | ||
| }).not.toMatchTypeOf<ClerkProviderProps>(); | ||
| }); | ||
nikosdouvlis marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| }); | ||
| describe('clerkJSVariant', () => { | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.