diff --git a/docs/_partials/organization-sync-options.mdx b/docs/_partials/organization-sync-options.mdx new file mode 100644 index 0000000000..1dc9e50a29 --- /dev/null +++ b/docs/_partials/organization-sync-options.mdx @@ -0,0 +1,68 @@ +The `organizationSyncOptions` property on the [`clerkMiddleware()`](#clerk-middleware-options) options +object has the type `OrganizationSyncOptions`, which has the following properties: + + + - `organizationPatterns` + - [Pattern](#pattern)\[] + + Specifies URL patterns that are organization-specific, containing an organization ID or slug as a path parameter. If a request + matches this path, the organization identifier will be used to set that org as active. + + If the route also matches the `personalAccountPatterns` prop, this prop takes precedence. + + Patterns must have a path parameter named either `:id` (to match a Clerk organization ID) or `:slug` (to match a Clerk organization slug). + + > [!WARNING] + > If the organization can't be activated—either because it doesn't exist or the user lacks access—the previously [active organization](!active-organization) will remain unchanged. Components must detect this case and provide an appropriate error and/or resolution pathway, such as calling `notFound()` or displaying an [``](/docs/reference/components/organization/organization-switcher). + + Common examples: + + - `["/orgs/:slug", "/orgs/:slug/(.*)"]` + - `["/orgs/:id", "/orgs/:id/(.*)"]` + - `["/app/:any/orgs/:slug", "/app/:any/orgs/:slug/(.*)"]` + + --- + + - `personalAccountPatterns` + - [Pattern](#pattern)\[] + + URL patterns for resources that exist within the context of a user's [personal account](/docs/guides/organizations/overview#allow-personal-accounts). + + If the route also matches the `organizationPattern` prop, the `organizationPattern` prop takes precedence. + + Common examples: + + - `["/me", "/me/(.*)"]` + - `["/user/:any", "/user/:any/(.*)"]` + + +### Pattern + +A `Pattern` is a `string` that represents the structure of a URL path. In addition to any valid URL, it may include: + +- Named path parameters prefixed with a colon (e.g., `:id`, `:slug`, `:any`). +- Wildcard token, `(.*)`, which matches the remainder of the path. + +#### Examples + +- `/orgs/:slug` + +| URL | Matches | `:slug` value | +| - | - | - | +| `/orgs/acmecorp` | ✅ | `acmecorp` | +| `/orgs` | ❌ | n/a | +| `/orgs/acmecorp/settings` | ❌ | n/a | + +- `/app/:any/orgs/:id` + +| URL | Matches | `:id` value | +| - | - | - | +| `/app/petstore/orgs/org_123` | ✅ | `org_123` | +| `/app/dogstore/v2/orgs/org_123` | ❌ | n/a | + +- `/personal-account/(.*)` + +| URL | Matches | +| - | - | +| `/personal-account/settings` | ✅ | +| `/personal-account` | ❌ | diff --git a/docs/getting-started/quickstart.js-backend.mdx b/docs/getting-started/quickstart.js-backend.mdx index 4c7be4fd79..8fbebf7735 100644 --- a/docs/getting-started/quickstart.js-backend.mdx +++ b/docs/getting-started/quickstart.js-backend.mdx @@ -419,8 +419,7 @@ The [`Auth`](/docs/reference/backend/types/auth-object) object contains importan ```tsx {{ filename: 'app/routes/profile.tsx' }} import { redirect } from 'react-router' - import { getAuth } from '@clerk/react-router/ssr.server' - import { createClerkClient } from '@clerk/react-router/api.server' + import { clerkClient, getAuth } from '@clerk/react-router/server' import type { Route } from './+types/profile' export async function loader(args: Route.LoaderArgs) { @@ -433,9 +432,7 @@ The [`Auth`](/docs/reference/backend/types/auth-object) object contains importan } // Use the JS Backend SDK's `getUser()` method to get the Backend User object - const user = await createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY }).users.getUser( - userId, - ) + const user = await clerkClient(args).users.getUser(userId) // Return the Backend User object return { diff --git a/docs/getting-started/quickstart.react-router.mdx b/docs/getting-started/quickstart.react-router.mdx index 0a1b9a6c30..ae873bfc0c 100644 --- a/docs/getting-started/quickstart.react-router.mdx +++ b/docs/getting-started/quickstart.react-router.mdx @@ -74,23 +74,36 @@ This tutorial assumes that you're using React Router **v7.1.2 or later** in fram CLERK_SECRET_KEY={{secret}} ``` - ## Configure `rootAuthLoader()` + ## Add `clerkMiddleware()` and `rootAuthLoader()` to your app - The `rootAuthLoader()` function provides access to authentication state in any React Router route. + `clerkMiddleware()` grants you access to user authentication state throughout your app. - The following code shows how to add this function to your `root.tsx` file. If you're using [Clerk's React Router quickstart](https://github.com/clerk/clerk-react-router-quickstart) or the [React Router template](https://reactrouter.com/start/framework/installation), most of this code will already be present. + React Router middleware requires opting in via a future flag. Add the following to your `react-router.config.ts` file: - To load additional data or configure options, see the [`rootAuthLoader()`](/docs/reference/react-router/root-auth-loader) reference. + ```ts {{ filename: 'react-router.config.ts' }} + import type { Config } from '@react-router/dev/config' - ```tsx {{ filename: 'app/root.tsx', mark: [1, [6, 8]], collapsible: true }} - import { rootAuthLoader } from '@clerk/react-router/ssr.server' + export default { + // ... + future: { + v8_middleware: true, + }, + } satisfies Config + ``` + + Then, add the following code to your `root.tsx` file to configure the `clerkMiddleware()` and `rootAuthLoader()` functions. + + To load additional data or configure options, see the [`clerkMiddleware()`](/docs/reference/react-router/clerk-middleware) reference. + + ```tsx {{ filename: 'app/root.tsx', mark: [4, [6, 8]], fold: [[1, 3], [10, 71]], collapsible: true }} import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router' import type { Route } from './+types/root' import stylesheet from './app.css?url' + import { clerkMiddleware, rootAuthLoader } from '@clerk/react-router/server' - export async function loader(args: Route.LoaderArgs) { - return rootAuthLoader(args) - } + export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()] + + export const loader = (args: Route.LoaderArgs) => rootAuthLoader(args) export const links: Route.LinksFunction = () => [ { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, @@ -169,18 +182,61 @@ This tutorial assumes that you're using React Router **v7.1.2 or later** in fram - [``](/docs/reference/components/user/user-button): Shows the signed-in user's avatar. Selecting it opens a dropdown menu with account management options. - [``](/docs/reference/components/unstyled/sign-in-button): An unstyled component that links to the sign-in page. In this example, since no props or [environment variables](/docs/guides/development/clerk-environment-variables) are set for the sign-in URL, this component links to the [Account Portal sign-in page](/docs/guides/customizing-clerk/account-portal#sign-in). - ```tsx {{ filename: 'app/root.tsx' }} - // Other imports - + ```tsx {{ filename: 'app/root.tsx', mark: [44, [46, 58]], fold: [[2, 42], [62, 87]] }} import { ClerkProvider, SignedIn, SignedOut, UserButton, SignInButton } from '@clerk/react-router' + import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router' + import { clerkMiddleware, rootAuthLoader } from '@clerk/react-router/server' + + import type { Route } from './+types/root' + import stylesheet from './app.css?url' + + export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()] + + export const loader = (args: Route.LoaderArgs) => rootAuthLoader(args) + + export const links: Route.LinksFunction = () => [ + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, + { + rel: 'preconnect', + href: 'https://fonts.gstatic.com', + crossOrigin: 'anonymous', + }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', + }, + { rel: 'stylesheet', href: stylesheet }, + ] + + export function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + {children} + + + + + ) + } + // Pull in the `loaderData` from the `rootAuthLoader()` function export default function App({ loaderData }: Route.ComponentProps) { return ( + // Pass the `loaderData` to the `` component
+ {/* Show the sign-in button when the user is signed out */} + {/* Show the user button when the user is signed in */} @@ -192,7 +248,32 @@ This tutorial assumes that you're using React Router **v7.1.2 or later** in fram ) } - // Rest of the root.tsx code + export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + let message = 'Oops!' + let details = 'An unexpected error occurred.' + let stack: string | undefined + + if (isRouteErrorResponse(error)) { + message = error.status === 404 ? '404' : 'Error' + details = + error.status === 404 ? 'The requested page could not be found.' : error.statusText || details + } else if (import.meta.env.DEV && error && error instanceof Error) { + details = error.message + stack = error.stack + } + + return ( +
+

{message}

+

{details}

+ {stack && ( +
+            {stack}
+          
+ )} +
+ ) + } ``` ## Create your first user diff --git a/docs/guides/development/verifying-oauth-access-tokens.react-router.mdx b/docs/guides/development/verifying-oauth-access-tokens.react-router.mdx index 1f561d75f7..8c58caf469 100644 --- a/docs/guides/development/verifying-oauth-access-tokens.react-router.mdx +++ b/docs/guides/development/verifying-oauth-access-tokens.react-router.mdx @@ -20,7 +20,7 @@ In the following example, the `acceptsToken` parameter is set to only accept `oa - If the token is valid, it returns the authenticated user's subject and their associated scopes for use in the application logic. ```tsx -import { getAuth } from '@clerk/react-router/ssr.server' +import { getAuth } from '@clerk/react-router/server' import type { Route } from './+types/profile' export async function loader(args: Route.LoaderArgs) { @@ -43,7 +43,7 @@ In the following example, the `acceptsToken` parameter is set to accept any toke - Otherwise, it logs that the request uses a machine token and specifies its type. ```tsx -import { getAuth } from '@clerk/react-router/ssr.server' +import { getAuth } from '@clerk/react-router/server' import type { Route } from './+types/profile' export async function loader(args: Route.LoaderArgs) { diff --git a/docs/guides/sessions/customize-session-tokens.mdx b/docs/guides/sessions/customize-session-tokens.mdx index 44165444b8..9b4576019b 100644 --- a/docs/guides/sessions/customize-session-tokens.mdx +++ b/docs/guides/sessions/customize-session-tokens.mdx @@ -190,13 +190,12 @@ This guide will show you how to customize a session token to include additional ```tsx {{ filename: 'app/routes/profile.tsx' }} import { redirect } from 'react-router' - import { getAuth } from '@clerk/react-router/ssr.server' - import { createClerkClient } from '@clerk/react-router/api.server' + import { getAuth } from '@clerk/react-router/server' import type { Route } from './+types/profile' export async function loader(args: Route.LoaderArgs) { // Use `getAuth()` to access `isAuthenticated` and the user's ID and session claims - const { isAuthenticated, userId, sessionClaims } = await getAuth(args) + const { isAuthenticated, sessionClaims } = await getAuth(args) // Protect the route by checking if the user is signed in if (!isAuthenticated) { diff --git a/docs/guides/sessions/session-tokens.mdx b/docs/guides/sessions/session-tokens.mdx index d5179e7cb2..4a19c97bcb 100644 --- a/docs/guides/sessions/session-tokens.mdx +++ b/docs/guides/sessions/session-tokens.mdx @@ -256,8 +256,7 @@ Then, when you need to access the other metadata fields, you can fetch them usin ```tsx {{ filename: 'app/routes/profile.tsx' }} import { redirect } from 'react-router' - import { getAuth } from '@clerk/react-router/ssr.server' - import { createClerkClient } from '@clerk/react-router/api.server' + import { clerkClient, getAuth } from '@clerk/react-router/server' import type { Route } from './+types/profile' export async function loader(args: Route.LoaderArgs) { @@ -269,10 +268,8 @@ Then, when you need to access the other metadata fields, you can fetch them usin return redirect('/sign-in?redirect_url=' + args.request.url) } - // Use the JS Backend SDK's `getUser()` method to get the Backend User object - const user = await createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY }).users.getUser( - userId, - ) + // Get the Backend User object + const user = await clerkClient(args).users.getUser(userId) // Return the Backend User object return { diff --git a/docs/guides/users/reading.react-router.mdx b/docs/guides/users/reading.react-router.mdx index 06289fae50..da2f6f3190 100644 --- a/docs/guides/users/reading.react-router.mdx +++ b/docs/guides/users/reading.react-router.mdx @@ -20,8 +20,7 @@ In the following example, the `userId` is passed to the JS Backend SDK's `getUse ```tsx {{ filename: 'app/routes/profile.tsx' }} import { redirect } from 'react-router' -import { getAuth } from '@clerk/react-router/ssr.server' -import { createClerkClient } from '@clerk/react-router/api.server' +import { clerkClient, getAuth } from '@clerk/react-router/server' import type { Route } from './+types/profile' export async function loader(args: Route.LoaderArgs) { @@ -33,10 +32,8 @@ export async function loader(args: Route.LoaderArgs) { return redirect('/sign-in?redirect_url=' + args.request.url) } - // Instantiate the JS Backend SDK and get the user's full `Backend User` object - const user = await createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY }).users.getUser( - userId, - ) + // Get the user's full `Backend User` object + const user = await clerkClient(args).users.getUser(userId) return { user: JSON.stringify(user), @@ -61,8 +58,7 @@ Unlike the previous example that loads data when the page loads, the following e ```tsx {{ filename: 'app/routes/profile-form.tsx' }} import { redirect, Form } from 'react-router' -import { getAuth } from '@clerk/react-router/ssr.server' -import { createClerkClient } from '@clerk/react-router/api.server' +import { clerkClient, getAuth } from '@clerk/react-router/server' import type { Route } from './+types/profile-form' export async function action(args: Route.ActionArgs) { @@ -78,10 +74,8 @@ export async function action(args: Route.ActionArgs) { const formData = await args.request.formData() const name = formData.get('name')?.toString() - // Instantiate the JS Backend SDK and get the user's full `Backend User` object - const user = await createClerkClient({ - secretKey: process.env.CLERK_SECRET_KEY, - }).users.getUser(userId) + // Get the user's full `Backend User` object + const user = await clerkClient(args).users.getUser(userId) return { name, diff --git a/docs/manifest.json b/docs/manifest.json index a6259f5b79..c2594d7318 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1888,6 +1888,10 @@ "title": "Overview", "href": "/docs/reference/react-router/overview" }, + { + "title": "`clerkMiddleware()`", + "href": "/docs/reference/react-router/clerk-middleware" + }, { "title": "`rootAuthLoader()`", "href": "/docs/reference/react-router/root-auth-loader" diff --git a/docs/reference/astro/clerk-middleware.mdx b/docs/reference/astro/clerk-middleware.mdx index dc3b174e0e..b58376136f 100644 --- a/docs/reference/astro/clerk-middleware.mdx +++ b/docs/reference/astro/clerk-middleware.mdx @@ -99,3 +99,7 @@ export const onRequest = clerkMiddleware((auth, context) => { ## `clerkMiddleware()` options + +### `OrganizationSyncOptions` + + diff --git a/docs/reference/express/clerk-middleware.mdx b/docs/reference/express/clerk-middleware.mdx index e91d957d0f..8fdc929a7e 100644 --- a/docs/reference/express/clerk-middleware.mdx +++ b/docs/reference/express/clerk-middleware.mdx @@ -86,3 +86,7 @@ app.listen(PORT, () => { A flag to enable Clerk's handshake flow, which helps verify the session state when a session JWT has expired. It issues a `307` redirect to refresh the session JWT if the user is still logged in. Defaults to `true`. + +#### `OrganizationSyncOptions` + + diff --git a/docs/reference/nextjs/clerk-middleware.mdx b/docs/reference/nextjs/clerk-middleware.mdx index 9c14fd112d..cac459a113 100644 --- a/docs/reference/nextjs/clerk-middleware.mdx +++ b/docs/reference/nextjs/clerk-middleware.mdx @@ -402,71 +402,4 @@ export default clerkMiddleware( ### `OrganizationSyncOptions` -The `organizationSyncOptions` property on the [`clerkMiddleware()`](#clerk-middleware-options) options -object has the type `OrganizationSyncOptions`, which has the following properties: - - - - `organizationPatterns` - - [Pattern](#pattern)\[] - - Specifies URL patterns that are organization-specific, containing an organization ID or slug as a path parameter. If a request - matches this path, the organization identifier will be used to set that org as active. - - If the route also matches the `personalAccountPatterns` prop, this prop takes precedence. - - Patterns must have a path parameter named either `:id` (to match a Clerk organization ID) or `:slug` (to match a Clerk organization slug). - - > [!WARNING] - > If the organization can't be activated—either because it doesn't exist or the user lacks access—the previously [active organization](!active-organization) will remain unchanged. Components must detect this case and provide an appropriate error and/or resolution pathway, such as calling `notFound()` or displaying an [``](/docs/reference/components/organization/organization-switcher). - - Common examples: - - - `["/orgs/:slug", "/orgs/:slug/(.*)"]` - - `["/orgs/:id", "/orgs/:id/(.*)"]` - - `["/app/:any/orgs/:slug", "/app/:any/orgs/:slug/(.*)"]` - - --- - - - `personalAccountPatterns` - - [Pattern](#pattern)\[] - - URL patterns for resources that exist within the context of a user's [personal account](/docs/guides/organizations/overview#allow-personal-accounts). - - If the route also matches the `organizationPattern` prop, the `organizationPattern` prop takes precedence. - - Common examples: - - - `["/me", "/me/(.*)"]` - - `["/user/:any", "/user/:any/(.*)"]` - - -### Pattern - -A `Pattern` is a `string` that represents the structure of a URL path. In addition to any valid URL, it may include: - -- Named path parameters prefixed with a colon (e.g., `:id`, `:slug`, `:any`). -- Wildcard token, `(.*)`, which matches the remainder of the path. - -#### Examples - -- `/orgs/:slug` - -| URL | Matches | `:slug` value | -| - | - | - | -| `/orgs/acmecorp` | ✅ | `acmecorp` | -| `/orgs` | ❌ | n/a | -| `/orgs/acmecorp/settings` | ❌ | n/a | - -- `/app/:any/orgs/:id` - -| URL | Matches | `:id` value | -| - | - | - | -| `/app/petstore/orgs/org_123` | ✅ | `org_123` | -| `/app/dogstore/v2/orgs/org_123` | ❌ | n/a | - -- `/personal-account/(.*)` - -| URL | Matches | -| - | - | -| `/personal-account/settings` | ✅ | -| `/personal-account` | ❌ | + diff --git a/docs/reference/nuxt/clerk-middleware.mdx b/docs/reference/nuxt/clerk-middleware.mdx index 1be730bed7..b9ac77bda3 100644 --- a/docs/reference/nuxt/clerk-middleware.mdx +++ b/docs/reference/nuxt/clerk-middleware.mdx @@ -180,3 +180,11 @@ Now, let's add authorization-based protection to the example so that you can see + } }) ``` + +## `clerkMiddleware()` options + + + +### `OrganizationSyncOptions` + + diff --git a/docs/reference/react-router/clerk-middleware.mdx b/docs/reference/react-router/clerk-middleware.mdx new file mode 100644 index 0000000000..247f7efdbf --- /dev/null +++ b/docs/reference/react-router/clerk-middleware.mdx @@ -0,0 +1,40 @@ +--- +title: '`clerkMiddleware()` | React Router' +description: The `clerkMiddleware()` function allows you to protect your React Router application using middleware. +sdk: react-router +--- + +The `clerkMiddleware()` helper integrates Clerk authentication into your React Router application through middleware. + +## Configure `clerkMiddleware()` + +1. React Router middleware requires opting in via a future flag. Add the following to your `react-router.config.ts` file: + + ```ts {{ filename: 'react-router.config.ts', mark: [[6, 8]] }} + import type { Config } from '@react-router/dev/config' + + export default { + // ... + + future: { + v8_middleware: true, + }, + } satisfies Config + ``` + +1. Export `clerkMiddleware()` from your root route file: + + ```tsx {{ filename: 'app/root.tsx' }} + import { clerkMiddleware } from '@clerk/react-router/server' + import type { Route } from './+types/root' + + export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()] + ``` + +## `clerkMiddleware()` options + + + +### `OrganizationSyncOptions` + + diff --git a/docs/reference/react-router/overview.mdx b/docs/reference/react-router/overview.mdx index 00a08d469a..c20e55f8e8 100644 --- a/docs/reference/react-router/overview.mdx +++ b/docs/reference/react-router/overview.mdx @@ -17,6 +17,7 @@ Because the React Router SDK is built on top of the [React SDK](/docs/reference/ The following references show how to integrate Clerk features into applications using React Router server functions and API routes. - [`getAuth()`](/docs/reference/react-router/get-auth) +- [`clerkMiddleware()`](/docs/reference/react-router/clerk-middleware) - [`rootAuthLoader()`](/docs/reference/react-router/root-auth-loader) ## React Router implementations diff --git a/docs/reference/react-router/root-auth-loader.mdx b/docs/reference/react-router/root-auth-loader.mdx index ffa39f5605..6440c310de 100644 --- a/docs/reference/react-router/root-auth-loader.mdx +++ b/docs/reference/react-router/root-auth-loader.mdx @@ -13,14 +13,12 @@ You can use the `rootAuthLoader()` in two different ways: - [Without a callback](#without-a-callback), which will just return the auth state - [With a callback function](#with-a-callback) to handle custom data loading while having access to auth state -You can also [pass configuration options](#pass-configuration-options) to `rootAuthLoader()` no matter which method you use. - ### Without a callback In your `root.tsx` file, add `rootAuthLoader()` to the `loader()` function. If your app doesn't have a `loader()` function yet, you'll need to add it manually. ```tsx {{ filename: 'app/root.tsx' }} -import { rootAuthLoader } from '@clerk/react-router/ssr.server' +import { rootAuthLoader } from '@clerk/react-router/server' import type { Route } from './+types/root' export async function loader(args: Route.LoaderArgs) { @@ -33,7 +31,7 @@ export async function loader(args: Route.LoaderArgs) { If you need to load in additional data, you can pass a callback to `rootAuthLoader()` that handles the route data loading with auth state. ```tsx {{ filename: 'app/root.tsx' }} -import { rootAuthLoader } from '@clerk/react-router/ssr.server' +import { rootAuthLoader } from '@clerk/react-router/server' import type { Route } from './+types/root' export async function loader(args: Route.LoaderArgs) { @@ -44,28 +42,3 @@ export async function loader(args: Route.LoaderArgs) { }) } ``` - -### Pass configuration options - -To pass configuration [options](#root-auth-loader-options) to `rootAuthLoader()`, you can pass an optional argument to the `rootAuthLoader()` function. - -```tsx {{ filename: 'app/root.tsx' }} -import { rootAuthLoader } from '@clerk/react-router/ssr.server' -import type { Route } from './+types/root' - -export async function loader(args: Route.LoaderArgs) { - return rootAuthLoader( - args, - ({ request, context, params }) => { - const { sessionId, userId, getToken } = request.auth - // Add logic to fetch data - return { yourData: 'here' } - }, - { signInUrl: '/sign-in' }, // Options - ) -} -``` - -## `rootAuthLoader()` options - -