Docs

Authentication across different domains

Warning

This guide addresses authentication across different domains with shared sessions. For example, example-site.com and example-site-admin.com.

Authentication across subdomains with shared sessions works by default with Clerk.

Clerk supports sharing sessions across different domains by adding one or many satellite domains to an application.

Your "primary" domain is where the authentication state lives, and satellite domains are able to securely read that state from the primary domain, enabling a seamless authentication flow across domains.

Users must complete both the sign-in and sign-up flows on the primary domain by using the <SignIn /> component or useSignIn() hook for sign-in and <SignUp /> component or useSignUp() hook for sign-up.

To access authentication state from a satellite domain, users will be transparently redirected to the primary domain. If users need to sign in, they must be redirected to a sign in flow hosted on the primary domain, then redirected back to the originating satellite domain. The same redirection process applies to sign-up flows.

How to add satellite domains

Warning

This feature is not available in production for free plans, though you can try it out free in development to see if it works for you. See the pricing page for more information.

Warning

Currently, multi-domain can be added to any Next.js or Remix application. For other React frameworks, multi-domain is still supported as long as you do not use server rendering or hydration.

To get started, you need to create an application from the Clerk Dashboard. Once you create an instance via the Clerk Dashboard, you will be prompted to choose a domain. This is your primary domain. For the purposes of this guide:

  • In production, the primary domain will be primary.dev
  • In development, the primary domain will be localhost:3000.

When building your sign-in flow, you must configure it to run within your primary application, e.g. on /sign-in.

Note

For more information about creating your application, see the setup guide.

Add your first satellite domain

To add a satellite domain:

  1. Navigate to the Clerk Dashboard
  2. In the navigation sidebar, select Domains.
  3. Select the Satellite tab.
  4. Select the Add satellite domain button and follow the instructions provided.

For the purposes of this guide:

  • In production, the satellite domain will be satellite.dev.
  • In development, the satellite domain will be localhost:3001.

Configure your satellite app

There are two ways that you can configure your Clerk satellite application to work with the primary domain:

  • Using environment variables
  • Using properties

Use the following tabs to select your preferred method. Clerk recommends using environment variables.

You can configure your satellite application by setting the following environment variables:

Note

In development, your publishable and secret keys will start with pk_test_ and sk_test respectively.

  • In the .env file associated with your primary domain:

    .env.local
    NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
    CLERK_SECRET_KEY=YOUR_SECRET_KEY
    NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
    NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
    .env
    CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
    CLERK_SECRET_KEY=YOUR_SECRET_KEY
    CLERK_SIGN_IN_URL=/sign-in
  • In the .env file associated with your other (satellite) domain:

    .env.local
    NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
    CLERK_SECRET_KEY=YOUR_SECRET_KEY
    NEXT_PUBLIC_CLERK_IS_SATELLITE=true
    # Production example:
    NEXT_PUBLIC_CLERK_DOMAIN=satellite.dev
    NEXT_PUBLIC_CLERK_SIGN_IN_URL=https://primary.dev/sign-in
    NEXT_PUBLIC_CLERK_SIGN_UP_URL=https://primary.dev/sign-up 
    
    # Development example:
    # NEXT_PUBLIC_CLERK_DOMAIN=http://localhost:3001
    # NEXT_PUBLIC_CLERK_SIGN_IN_URL=http://localhost:3000/sign-in
    # NEXT_PUBLIC_CLERK_SIGN_UP_URL=http://localhost:3000/sign-up
    .env
    CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
    CLERK_SECRET_KEY=YOUR_SECRET_KEY
    CLERK_IS_SATELLITE=true
    # Production example:
    CLERK_DOMAIN=satellite.dev
    CLERK_SIGN_IN_URL=https://primary.dev/sign-in
    CLERK_SIGN_UP_URL=https://primary.dev/sign-up
    
    # Development example:
    # CLERK_DOMAIN=http://localhost:3001
    # CLERK_SIGN_IN_URL=http://localhost:3000/sign-in
    # CLERK_SIGN_UP_URL=http://localhost:3000/sign-up
  • You will also need to add the allowedRedirectOrigins property to <ClerkProvider /> on your primary domain app to ensure that the redirect back from primary to satellite domain works correctly. For example:

    app/layout.tsx
    import { ClerkProvider } from '@clerk/nextjs'
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <ClerkProvider allowedRedirectOrigins={['http://localhost:3001']}>{children}</ClerkProvider>
          </body>
        </html>
      )
    }
    app/layout.tsx
    import { ClerkProvider } from '@clerk/nextjs'
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <ClerkProvider allowedRedirectOrigins={['https://satellite.dev']}>{children}</ClerkProvider>
          </body>
        </html>
      )
    }

You can configure your satellite application by setting the following properties:

  • isSatellite - Defines the app as a satellite app when true.
  • domain - Sets the domain of the satellite application. This is required since we cannot figure this out by your publishable key, since it is the same for all of your multi-domain apps.
  • signInUrl - This url will be used when signing in on your satellite application and needs to point to your primary application. This option is optional for production instances and required for development instances.
  • signUpUrl - This url will be used for signing up on your satellite application and needs to point to your primary application. This option is optional for production instances and required for development instances.
  • allowedRedirectOrigins - This is a list of origins that are allowed to redirect back to from the primary domain.

Tip

The URL parameter that can be passed to isSatellite and domain is the request url for server-side usage or the current location for client usage.

In a Next.js application, you must set the properties in the <ClerkProvider> component and in your clerkMiddleware().

  • In the Next project associated with your primary domain, only the signInUrl prop needs to be configured as shown in the following example:

    Important

    You should set your CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY in your environment variables even if you're using props to configure satellite domains.

    app/layout.tsx
    import { ClerkProvider } from '@clerk/nextjs'
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      const primarySignInUrl = '/sign-in'
      const primarySignUpUrl = '/sign-up'
      const satelliteUrl = 'https://satellite.dev'
    
      return (
        <html lang="en">
          <body>
            <ClerkProvider
              signInUrl={primarySignInUrl}
              signUpUrl={primarySignUpUrl}
              allowedRedirectOrigins={[satelliteUrl]}
            >
              <p>Satellite Next.js app</p>
              {children}
            </ClerkProvider>
          </body>
        </html>
      )
    }
    _app.tsx
    import { ClerkProvider } from '@clerk/nextjs'
    import Head from 'next/head'
    
    export default function App({ Component, pageProps }) {
      const primarySignInUrl = '/sign-in'
      const primarySignUpUrl = '/sign-up'
      const satelliteUrl = 'https://satellite.dev'
    
      return (
        <ClerkProvider
          signInUrl={primarySignInUrl}
          signUpUrl={primarySignUpUrl}
          allowedRedirectOrigins={[satelliteUrl]}
          {...pageProps}
        >
          <Head>
            <title>Satellite Next.js app</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          </Head>
          <Component {...pageProps} />
        </ClerkProvider>
      )
    }
  • In the Next project associated with your satellite domain, configure your <ClerkProvider> as shown in the following example:

    app/layout.tsx
    import { ClerkProvider } from '@clerk/nextjs'
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      const primarySignInUrl = 'https://primary.dev/sign-in'
      const primarySignUpUrl = 'https://primary.dev/sign-up'
      // Or, in development:
      // const primarySignInUrl = 'http:localhost:3000/sign-in';
      // const primarySignUpUrl = 'http:localhost:3000/sign-up';
    
      return (
        <html lang="en">
          <body>
            <ClerkProvider
              isSatellite
              domain={(url) => url.host}
              signInUrl={primarySignInUrl}
              signUpUrl={primarySignUpUrl}
            >
              <title>Satellite Next.js app</title>
              <meta name="viewport" content="width=device-width, initial-scale=1.0" />
              {children}
            </ClerkProvider>
          </body>
        </html>
      )
    }
    _app.tsx
    import { ClerkProvider } from '@clerk/nextjs'
    import Head from 'next/head'
    
    export default function App({ Component, pageProps }) {
      const primarySignInUrl = 'https://primary.dev/sign-in'
      const primarySignUpUrl = 'https://primary.dev/sign-up'
      // Or, in development:
      // const primarySignInUrl = 'http:localhost:3000/sign-in';
      // const primarySignUpUrl = 'http:localhost:3000/sign-up';
    
      return (
        <ClerkProvider
          isSatellite
          domain={(url) => url.host}
          signInUrl={primarySignInUrl}
          signUpUrl={primarySignUpUrl}
          {...pageProps}
        >
          <Head>
            <title>Satellite Next.js app</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          </Head>
          <Component {...pageProps} />
        </ClerkProvider>
      )
    }

And the middleware associated with your satellite domain should look like this:

middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

// Set the homepage as a public route
const isPublicRoute = createRouteMatcher(['/'])

// Set the necessary options for a satellite application
const options = {
  isSatellite: true,
  signInUrl: 'https://primary.dev/sign-in',
  signUpUrl: 'https://primary.dev/sign-up',
  // Or, in development:
  // signInUrl: 'http://localhost:3000/sign-in',
  // signUpUrl: 'http://localhost:3000/sign-up',
  domain: 'https://satellite.dev',
  // Or, in development:
  // domain: 'http://localhost:3001',
}

export default clerkMiddleware(async (auth, req) => {
  if (isPublicRoute(req)) return // if it's a public route, do nothing
  await auth.protect() // for any other route, require auth
}, options)

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ],
}

In a Remix application, you must set the properties in the ClerkApp wrapper.

  • In the root file associated with your primary domain, you only need to configure the signInUrl prop:

    root.tsx
    export const loader = (args) => {
      return rootAuthLoader(
        args,
        ({ request }) => {
          const { userId, sessionId, getToken } = request.auth
          return json({
            message: `Hello from the root loader :)`,
            ENV: getBrowserEnvironment(),
          })
        },
        {
          loadUser: true,
          signInUrl: '/sign-in',
          signUpUrl: '/sign-up',
          allowedRedirectOrigins: ['https://satellite.dev'],
        } as const,
      )
    }
    
    export default ClerkApp(App, {
      signInUrl: '/sign-in',
      signUpUrl: '/sign-up',
    })
  • In the root file associated with your satellite domain, configure ClerkApp as shown in the following example:

    root.tsx
    export const loader = (args) => {
      return rootAuthLoader(
        args,
        ({ request }) => {
          const { userId, sessionId, getToken } = request.auth
          return json({
            message: `Hello from the root loader :)`,
            ENV: getBrowserEnvironment(),
          })
        },
        {
          loadUser: true,
          signInUrl: 'https://primary.dev/sign-in',
          signUpUrl: 'https://primary.dev/sign-up',
          // Or, in development:
          // signInUrl: 'http:localhost:3000/sign-in',
          // signUpUrl: 'http:localhost:3000/sign-up',
          isSatellite: true,
          domain: (url) => url.host,
        } as const,
      )
    }
    
    export default ClerkApp(App, {
      isSatellite: true,
      domain: (url) => url.host,
      signInUrl: 'https://primary.dev/sign-in',
      signUpUrl: 'https://primary.dev/sign-up',
      // Or, in development:
      // signInUrl: 'http:localhost:3000/sign-in',
      // signUpUrl: 'http:localhost:3000/sign-up',
    })

Ready to go 🎉

Your satellite application should now be able to access the authentication state from your satellite domain!

You can see it in action by:

  1. Visiting the primary domain and signing in.
  2. Visiting the satellite domain.
  3. You now have an active session in the satellite domain, so you can see the <UserProfile /> component and update your information.

You can repeat this process and create as many satellite applications as you need.

If you have any questions about satellite domains, or you're having any trouble setting this up, contact support@clerk.com

Feedback

What did you think of this content?

Last updated on