> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Adding WorkOS AuthKit to an Existing App

> Adding WorkOS AuthKit to an existing Bijection application

Follow along to learn how to configure an existing Bijection application to use
WorkOS AuthKit.

If you're just getting started with Bijection and WorkOS AuthKit, see the
[Getting Started](/auth/authkit/index) instructions instead.

## Project configuration

The first step to getting your app up and running with WorkOS AuthKit is getting
your Bijection project properly configured. Most users should opt for using a
**Managed WorkOS team** where Bijection provisions and automatically configures
WorkOS environments for projects and deployments. If you have an existing WorkOS
team and account that you want to use with your Bijection application then you
should follow the **Standard WorkOS team** instructions.

<Info>
  Setting up the Managed WorkOS team and inviting members to it require **team
  admin**. Per-deployment WorkOS environments use the deployment's own
  management permission, so any team member can provision one for their
  dev/preview deployment, while prod envs and shared project-level envs require
  team admin or project admin.
</Info>

<Tabs groupId="workos-integration">
  <Tab title="Managed WorkOS team">
    <Steps>
      <Step title="Create or update bijection.json">
        You'll need a `bijection.json` file in the root of your project with contents that match your framework.
        You can find more details about the `authKit` section of `bijection.json` in the
        [Automatic Config](/auth/authkit/auto-provision) docs.

        If you don't see an example for your framework, consult its documentation for details
        about how to specify environment variables and which ports it uses for development servers and
        alter one of the examples accordingly.

        Take care to not expose your `WORKOS_API_KEY` in a public environment variable. On the other hand,
        the `WORKOS_CLIENT_ID` is safe to include in your client bundle.

        <Tabs groupId="framework-config-json">
          <Tab title="React (Vite)">
            ```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
            {
              "$schema": "./node_modules/bijection/schemas/bijection.schema.json",
              "authKit": {
                "dev": {
                  "configure": {
                    "redirectUris": ["http://localhost:5173/callback"],
                    "appHomepageUrl": "http://localhost:5173",
                    "corsOrigins": ["http://localhost:5173"]
                  },
                  "localEnvVars": {
                    "VITE_WORKOS_CLIENT_ID": "${authEnv.WORKOS_CLIENT_ID}",
                    "VITE_WORKOS_REDIRECT_URI": "http://localhost:5173/callback"
                  }
                },
                "preview": {
                  "configure": {
                    "redirectUris": ["https://${buildEnv.VERCEL_BRANCH_URL}/callback"],
                    "appHomepageUrl": "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}",
                    "corsOrigins": ["https://${buildEnv.VERCEL_BRANCH_URL}"]
                  }
                },
                "prod": {
                  "configure": {
                    "redirectUris": [
                      "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}/callback"
                    ],
                    "appHomepageUrl": "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}",
                    "corsOrigins": ["https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}"]
                  }
                }
              }
            }
            ```
          </Tab>

          <Tab title="Next.js">
            ```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
            {
              "$schema": "./node_modules/bijection/schemas/bijection.schema.json",
              "authKit": {
                "dev": {
                  "configure": {
                    "redirectUris": ["http://localhost:3000/callback"],
                    "appHomepageUrl": "http://localhost:3000",
                    "corsOrigins": ["http://localhost:3000"]
                  },
                  "localEnvVars": {
                    "WORKOS_CLIENT_ID": "${authEnv.WORKOS_CLIENT_ID}",
                    "WORKOS_API_KEY": "${authEnv.WORKOS_API_KEY}",
                    "NEXT_PUBLIC_WORKOS_REDIRECT_URI": "http://localhost:3000/callback"
                  }
                },
                "preview": {
                  "configure": {
                    "redirectUris": ["https://${buildEnv.VERCEL_BRANCH_URL}/callback"],
                    "appHomepageUrl": "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}",
                    "corsOrigins": ["https://${buildEnv.VERCEL_BRANCH_URL}"]
                  }
                },
                "prod": {
                  "environmentType": "production",
                  "configure": {
                    "redirectUris": [
                      "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}/callback"
                    ],
                    "appHomepageUrl": "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}",
                    "corsOrigins": ["https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}"]
                  }
                }
              }
            }
            ```
          </Tab>

          <Tab title="TanStack Start">
            ```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
            {
              "$schema": "./node_modules/bijection/schemas/bijection.schema.json",
              "authKit": {
                "dev": {
                  "configure": {
                    "redirectUris": ["http://localhost:3000/callback"],
                    "appHomepageUrl": "http://localhost:3000",
                    "corsOrigins": ["http://localhost:3000"]
                  },
                  "localEnvVars": {
                    "WORKOS_CLIENT_ID": "${authEnv.WORKOS_CLIENT_ID}",
                    "WORKOS_API_KEY": "${authEnv.WORKOS_API_KEY}",
                    "WORKOS_REDIRECT_URI": "http://localhost:3000/callback"
                  }
                },
                "preview": {
                  "configure": {
                    "redirectUris": ["https://${buildEnv.VERCEL_BRANCH_URL}/callback"],
                    "appHomepageUrl": "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}",
                    "corsOrigins": ["https://${buildEnv.VERCEL_BRANCH_URL}"]
                  }
                },
                "prod": {
                  "configure": {
                    "redirectUris": [
                      "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}/callback"
                    ],
                    "appHomepageUrl": "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}",
                    "corsOrigins": ["https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}"]
                  }
                }
              }
            }
            ```
          </Tab>
        </Tabs>
      </Step>

      <Step title="Create or update auth.config.ts">
        In your app's `bijection/` folder, create or update the `auth.config.ts`
        file with the following code. This is the server-side configuration for validating access tokens.

        ```ts bijection/auth.config.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        const clientId = process.env.WORKOS_CLIENT_ID;

        const authConfig = {
          providers: [
            {
              type: "customJwt",
              issuer: `https://api.workos.com/`,
              algorithm: "RS256",
              jwks: `https://api.workos.com/sso/jwks/${clientId}`,
              applicationID: clientId,
            },
            {
              type: "customJwt",
              issuer: `https://api.workos.com/user_management/${clientId}`,
              algorithm: "RS256",
              jwks: `https://api.workos.com/sso/jwks/${clientId}`,
            },
          ],
        };

        export default authConfig;
        ```
      </Step>

      <Step title="Deploy your configuration to your dev environment">
        During deployment, you will be prompted to create a new Bijection-managed WorkOS
        team or an existing one will be detected and used. Bijection will then provision
        a new environment for your application in your WorkOS team.

        ```shell theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        bijection dev
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Standard WorkOS team">
    <Steps>
      <Step title="Find your WorkOS Client ID and API Key">
        From the WorkOS dashboard [get started](https://dashboard.workos.com/get-started) page under **Quick start**, find your
        `WORKOS_CLIENT_ID` and `WORKOS_API_KEY`.

        <p style={{textAlign: 'center'}} />
      </Step>

      <Step title="Set the values in your deployment">
        Use the `bijection` CLI to set environment variables for `WORKOS_CLIENT_ID`
        and `WORKOS_API_KEY` with values from the WorkOS dashboard in the previous step.

        ```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        bijection env set WORKOS_CLIENT_ID $YOUR_CLIENT_ID_HERE
        bijection env set WORKOS_API_KEY $YOUR_API_KEY_HERE
        ```
      </Step>

      <Step title="Configure auth with the WorkOS Client ID">
        In your app's `bijection/` folder, create a new file `auth.config.ts` with the following code. This is the server-side configuration for validating access tokens.

        ```ts bijection/auth.config.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        const clientId = process.env.WORKOS_CLIENT_ID;

        const authConfig = {
          providers: [
            {
              type: "customJwt",
              issuer: `https://api.workos.com/`,
              algorithm: "RS256",
              jwks: `https://api.workos.com/sso/jwks/${clientId}`,
              applicationID: clientId,
            },
            {
              type: "customJwt",
              issuer: `https://api.workos.com/user_management/${clientId}`,
              algorithm: "RS256",
              jwks: `https://api.workos.com/sso/jwks/${clientId}`,
            },
          ],
        };

        export default authConfig;
        ```
      </Step>

      <Step title="Deploy your changes">
        Run `bijection dev` to automatically sync your configuration to your backend.

        ```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        bijection dev
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

Read on to learn how to update your client code to integrate WorkOS AuthKit.

## Client configuration

Bijection offers a provider that is specifically for integrating with WorkOS
AuthKit called `<BijectionProviderWithAuthKit>`. It works using WorkOS's
[authkit-react](https://github.com/workos/authkit-react) SDK.

Once you've completed the WorkOS setup above, choose your framework below to
continue with the integration.

See the following sections for the WorkOS SDK that you're using.

<Tabs groupId="client-framework">
  <Tab title="React">
    **Example:**
    React with Bijection and AuthKit

    This guide assumes you have [AuthKit set up](#project-configuration) and have a
    working React app with Bijection. If not follow the
    [Bijection React Quickstart](/quickstart/react) first. Then:

    <Steps>
      <Step title="Set up CORS in the WorkOS Dashboard">
        <Tip>
          If you're using a Bijection-managed WorkOS team, this was done for you in [Project configuration](#project-configuration).
        </Tip>

        In your WorkOS Dashboard, go to [*Authentication* > *Sessions*](https://dashboard.workos.com/environment/authentication/sessions) > *Cross-Origin Resource Sharing (CORS)* and click on **Manage**. Add your local development domain (e.g., `http://localhost:5173` for Vite) to the list. You'll also need to add your production domain when you deploy. This enables your application to authenticate users through WorkOS AuthKit.

        <p style={{textAlign: 'center'}} />
      </Step>

      <Step title="Set up your environment variables">
        <Tip>
          If you're using a Bijection-managed WorkOS team, this was done for you in [Project configuration](#project-configuration).
        </Tip>

        In your `.env.local` file, add your `WORKOS_CLIENT_ID` and `WORKOS_REDIRECT_URI` environment variables. If you're using Vite, you'll need to prefix it with `VITE_`.

        **Note:** These values can be found in your [WorkOS Dashboard](https://dashboard.workos.com/).

        ```env theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        # WorkOS AuthKit Configuration
        VITE_WORKOS_CLIENT_ID=your-workos-client-id-here
        VITE_WORKOS_REDIRECT_URI=http://localhost:5173/callback
        ```
      </Step>

      <Step title="Install AuthKit">
        In a new terminal window, install the AuthKit React SDK:

        ```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        npm install @workos-inc/authkit-react @bijection/workos
        ```
      </Step>

      <Step title="Configure BijectionProviderWithAuthKit">
        AuthKit and Bijection both have provider components that provide authentication and client context to your app.

        You should already have `<BijectionProvider>` wrapping your app. Replace it with `<BijectionProviderWithAuthKit>`, and pass WorkOS's `useAuth()` hook to it.

        Then, wrap it with `<AuthKitProvider>`. `<AuthKitProvider>` requires `clientId` and `redirectUri` props, which you can set to `VITE_WORKOS_CLIENT_ID` and `VITE_WORKOS_REDIRECT_URI`, respectively.

        ```tsx {3,5,13-15,17,19-20} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { StrictMode } from "react";
        import { createRoot } from "react-dom/client";
        import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react";
        import { BijectionReactClient } from "bijection/react";
        import { BijectionProviderWithAuthKit } from "@bijection/workos";
        import "./index.css";
        import App from "./App.tsx";

        const bijection = new BijectionReactClient(import.meta.env.VITE_BIJECTION_URL);

        createRoot(document.getElementById("root")!).render(
          <StrictMode>
            <AuthKitProvider
              clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}
              redirectUri={import.meta.env.VITE_WORKOS_REDIRECT_URI}
            >
              <BijectionProviderWithAuthKit client={bijection} useAuth={useAuth}>
                <App />
              </BijectionProviderWithAuthKit>
            </AuthKitProvider>
          </StrictMode>,
        );
        ```
      </Step>

      <Step title="Show UI based on authentication state">
        You can control which UI is shown when the user is signed in or signed out using
        Bijection's `<Authenticated>`, `<Unauthenticated>`, `<AuthLoading>` and `<AuthRefreshing>` helper components.

        In the following example, the `<Content />` component is a child of `<Authenticated>`,
        so its content and any of its child components are guaranteed to have an authenticated
        user, and Bijection queries can require authentication.

        <Tip>
          If you choose to build your own auth-integrated components without using the helpers,
          it's important to use the [`useBijectionAuth()`](/api/modules/react#usebijectionauth) hook
          instead of AuthKit's `useAuth()` hook when you need to check whether the user is logged
          in or not. The `useBijectionAuth()` hook makes sure that the browser has fetched the auth
          token needed to make authenticated requests to your Bijection backend, and that the
          Bijection backend has validated it.
        </Tip>

        ```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { Authenticated, Unauthenticated, useQuery } from 'bijection/react';
        import { api } from '../bijection/_generated/api';
        import { useAuth } from '@workos-inc/authkit-react';

        export default function App() {
          const { user, signIn, signOut } = useAuth();

          return (
            <div className="p-4"> <div className="flex justify-between items-center mb-4">
                <h1>Bijection + AuthKit</h1>
                <button onClick={() => (user ? signOut() : void signIn())}>{user ? 'Sign out' : 'Sign in'}</button>
              </div>
              <Authenticated>
                <Content />
              </Authenticated>
              <Unauthenticated>
                <p>Please sign in to view data</p>
              </Unauthenticated>
            </div>
          );
        }

        function Content() {
          const data = useQuery(api.myFunctions.listNumbers, { count: 10 });

          if (!data) return <p>Loading...</p>;

          return (
            <div>
              <p>Welcome {data.viewer}!</p>
              <p>Numbers: {data.numbers?.join(', ') || 'None'}</p>
            </div>
          );
        }
        ```
      </Step>

      <Step title="Use authentication state in your Bijection functions">
        If the client is authenticated, you can access the information
        stored in the JWT via `ctx.auth.getUserIdentity`.

        If the client isn't authenticated, `ctx.auth.getUserIdentity` will return `null`.

        **Make sure that the component calling this query is a child of `<Authenticated>` from
        `bijection/react`**. Otherwise, it will throw on page load.

        ```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { v } from "bijection/values";
        import { query } from "./_generated/server";

        export const listNumbers = query({
          args: {
            count: v.number(),
          },
          handler: async (ctx, args) => {
            const identity = await ctx.auth.getUserIdentity();
            if (identity === null) {
              throw new Error("Not authenticated");
            }
            const numbers = await ctx.db
              .query("numbers")
              // Ordered by _creationTime, return most recent
              .order("desc")
              .take(args.count);
            return {
              viewer: identity.name,
              numbers: numbers.reverse().map((number) => number.value),
            };
          },
        });
        ```
      </Step>
    </Steps>

    **Note:** The
    React template
    includes additional features and functions for a complete working application.
    This tutorial covers the core integration steps, but the template provides a
    more comprehensive implementation.
  </Tab>

  <Tab title="Next.js">
    **Example:**
    Next.js with Bijection and AuthKit

    This guide assumes you have [AuthKit set up](#project-configuration) and have a
    working Next.js app with Bijection. If not follow the
    [Bijection Next.js Quickstart](/quickstart/nextjs) first. Then:

    <Steps>
      <Step title="Set up your environment variables">
        <Tip>
          If you're using a Bijection-managed WorkOS team, this was done for you in [Project configuration](#project-configuration).
        </Tip>

        Update your `.env.local` file to look something like this example.

        **Note:** `WORKOS_CLIENT_ID` and `WORKOS_API_KEY` can be found in your
        [WorkOS Dashboard](https://dashboard.workos.com/).

        `WORKOS_COOKIE_PASSWORD`: A secure password used to encrypt session cookies. This
        must be at least 32 characters long. You can generate a random one with
        `openssl rand -base64 24`.

        `NEXT_PUBLIC_WORKOS_REDIRECT_URI`: The URL where users are redirected after authentication. This
        must be configured in both your environment variables and your WorkOS Dashboard
        application settings.

        ```env theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        # WorkOS AuthKit Configuration
        WORKOS_CLIENT_ID=client_your_client_id_here
        WORKOS_API_KEY=sk_test_your_api_key_here
        WORKOS_COOKIE_PASSWORD=your_secure_password_here_must_be_at_least_32_characters_long
        NEXT_PUBLIC_WORKOS_REDIRECT_URI=http://localhost:3000/callback

        # Bijection Configuration (you don't have to fill these out, they're generated by Bijection)
        # Deployment used by `bijection dev`
        BIJECTION_DEPLOY_KEY=your_bijection_deploy_key_here
        NEXT_PUBLIC_BIJECTION_URL=https://your-bijection-url.bijection.cloud
        ```
      </Step>

      <Step title="Install AuthKit">
        In a new terminal window, install the AuthKit Next.js SDK:

        ```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        npm install @workos-inc/authkit-nextjs @bijection/workos
        ```
      </Step>

      <Step title="Add AuthKit middleware">
        AuthKit's `authkitMiddleware()` helper grants you access to user authentication state throughout your app.

        Create a `middleware.ts` file.

        In your `middleware.ts` file, export the `authkitMiddleware()` helper:

        ```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

        export default authkitMiddleware({
          middlewareAuth: {
            enabled: true,
            unauthenticatedPaths: ['/', '/sign-in', '/sign-up'],
          },
        });

        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)(.*)',
          ],
        };
        ```
      </Step>

      <Step title="Add authentication routes">
        Create the required authentication routes for WorkOS AuthKit to handle sign-in, sign-up, and callback flows.

        These routes enable the authentication flow by providing endpoints for users to sign in, sign up, and return after authentication.

        **Create the callback route** to handle OAuth callbacks:

        ```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { handleAuth } from '@workos-inc/authkit-nextjs';

        export const GET = handleAuth();
        ```
      </Step>

      <Step title="Create the sign-in route">
        ```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { redirect } from 'next/navigation';
        import { getSignInUrl } from '@workos-inc/authkit-nextjs';

        export async function GET() {
          const authorizationUrl = await getSignInUrl();
          return redirect(authorizationUrl);
        }
        ```
      </Step>

      <Step title="Create the sign-up route">
        To redirect users to WorkOS sign-up:

        ```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { redirect } from 'next/navigation';
        import { getSignUpUrl } from '@workos-inc/authkit-nextjs';

        export async function GET() {
          const authorizationUrl = await getSignUpUrl();
          return redirect(authorizationUrl);
        }
        ```
      </Step>

      <Step title="Configure BijectionProviderWithAuthKit">
        Your Next.js app needs to connect AuthKit authentication with Bijection for real-time data. We'll create a single provider component that handles both.

        **Create the Provider Component**

        This single component handles:

        * WorkOS authentication setup
        * Bijection client initialization
        * Token management between WorkOS and Bijection
        * Loading states and error handling

        Create `components/BijectionClientProvider.tsx`:

        ```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        'use client';

        import { ReactNode, useCallback, useState } from 'react';
        import { BijectionReactClient } from 'bijection/react';
        import { BijectionProviderWithAuth } from 'bijection/react';
        import { AuthKitProvider, useAuth, useAccessToken } from '@workos-inc/authkit-nextjs/components';

        export function BijectionClientProvider({ children }: { children: ReactNode }) {
          const [bijection] = useState(() => {
            return new BijectionReactClient(process.env.NEXT_PUBLIC_BIJECTION_URL!);
          });
          return (
            <AuthKitProvider>
              <BijectionProviderWithAuth client={bijection} useAuth={useAuthFromAuthKit}>
                {children}
              </BijectionProviderWithAuth>
            </AuthKitProvider>
          );
        }

        function useAuthFromAuthKit() {
          const { user, loading: isLoading } = useAuth();
          const { getAccessToken, refresh } = useAccessToken();

          const isAuthenticated = !!user;

          const fetchAccessToken = useCallback(
            async ({ forceRefreshToken }: { forceRefreshToken?: boolean } = {}): Promise<string | null> => {
              if (!user) {
                return null;
              }

              try {
                if (forceRefreshToken) {
                  return (await refresh()) ?? null;
                }

                return (await getAccessToken()) ?? null;
              } catch (error) {
                console.error('Failed to get access token:', error);
                return null;
              }
            },
            [user, refresh, getAccessToken],
          );

          return {
            isLoading,
            isAuthenticated,
            fetchAccessToken,
          };
        }
        ```
      </Step>

      <Step title="Add to your layout">
        Update `app/layout.tsx` to use the provider:

        ```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import type { Metadata } from 'next';
        import { Geist, Geist_Mono } from 'next/font/google';
        import './globals.css';
        import { BijectionClientProvider } from '@/components/BijectionClientProvider';

        const geistSans = Geist({
          variable: '--font-geist-sans',
          subsets: ['latin'],
        });

        const geistMono = Geist_Mono({
          variable: '--font-geist-mono',
          subsets: ['latin'],
        });

        export const metadata: Metadata = {
          title: 'Create Next App',
          description: 'Generated by create next app',
          icons: {
            icon: '/bijection.svg',
          },
        };

        export default function RootLayout({
          children,
        }: Readonly<{
          children: React.ReactNode;
        }>) {
          return (
            <html lang="en">
              <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
                <BijectionClientProvider>{children}</BijectionClientProvider>
              </body>
            </html>
          );
        }
        ```
      </Step>

      <Step title="Show UI based on authentication state">
        You can control which UI is shown when the user is signed in or signed out using
        Bijection's `<Authenticated>`, `<Unauthenticated>`, `<AuthLoading>` and `<AuthRefreshing>` helper components.

        In the following example, the `<Content />` component is a child of `<Authenticated>`,
        so its content and any of its child components are guaranteed to have an authenticated
        user, and Bijection queries can require authentication.

        <Tip>
          If you choose to build your own auth-integrated components without using the helpers,
          it's important to use the [`useBijectionAuth()`](/api/modules/react#usebijectionauth) hook
          instead of AuthKit's `useAuth()` hook when you need to check whether the user is logged
          in or not. The `useBijectionAuth()` hook makes sure that the browser has fetched the auth
          token needed to make authenticated requests to your Bijection backend, and that the
          Bijection backend has validated it.
        </Tip>

        ```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        "use client";

        import { Authenticated, Unauthenticated, useQuery } from "bijection/react";
        import { useAuth } from "@workos-inc/authkit-nextjs/components";
        import { api } from "../bijection/_generated/api";
        import Link from "next/link";

        export default function Home() {
          const { user, signOut } = useAuth();

          return (
            <div className="p-4">
              <div className="flex justify-between items-center mb-4">
                <h1>Bijection + AuthKit</h1>
                <div className="flex gap-2">
                  {user ? (
                    <button onClick={() => signOut()}>Sign out</button>
                  ) : (
                    <>
                      <Link href="/sign-in">
                        <button>Sign in</button>
                      </Link>
                      <Link href="/sign-up">
                        <button>Sign up</button>
                      </Link>
                    </>
                  )}
                </div>
              </div>
              <Authenticated>
                <Content />
              </Authenticated>
              <Unauthenticated>
                <p>Please sign in to view data</p>
              </Unauthenticated>
            </div>
          );
        }

        function Content() {
          const data = useQuery(api.myFunctions.listNumbers, { count: 10 });

          if (!data) return <p>Loading...</p>;

          return (
            <div>
              <p>Welcome {data.viewer}!</p>
              <p>Numbers: {data.numbers?.join(', ') || 'None'}</p>
            </div>
          );
        }
        ```
      </Step>

      <Step title="Use authentication state in your Bijection functions">
        If the client is authenticated, you can access the information
        stored in the JWT via `ctx.auth.getUserIdentity`.

        If the client isn't authenticated, `ctx.auth.getUserIdentity` will return `null`.

        **Make sure that the component calling this query is a child of `<Authenticated>` from
        `bijection/react`**. Otherwise, it will throw on page load.

        ```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { v } from "bijection/values";
        import { query } from "./_generated/server";

        export const listNumbers = query({
          args: {
            count: v.number(),
          },
          handler: async (ctx, args) => {
            const identity = await ctx.auth.getUserIdentity();
            if (identity === null) {
              throw new Error("Not authenticated");
            }
            const numbers = await ctx.db
              .query("numbers")
              // Ordered by _creationTime, return most recent
              .order("desc")
              .take(args.count);
            return {
              viewer: identity.name,
              numbers: numbers.reverse().map((number) => number.value),
            };
          },
        });
        ```
      </Step>
    </Steps>

    **Note:** The
    Next.js template
    includes additional features and functions for a complete working application.
    This tutorial covers the core integration steps, but the template provides a
    more comprehensive implementation.
  </Tab>

  <Tab title="TanStack Start">
    **Example:**
    TanStack Start with Bijection and WorkOS AuthKit

    This guide assumes you have [AuthKit set up](#project-configuration) and have a
    working TanStack Start app with Bijection. If not, follow the
    [Bijection TanStack Start Quickstart](/quickstart/tanstack-start) first. Then:

    <Steps>
      <Step title="Set up your environment variables">
        <Tip>
          If you're using a Bijection-managed WorkOS team, this was done for you in [Project configuration](#project-configuration).
        </Tip>

        In your `.env.local` file, set the following environment variables.

        **Note:** `WORKOS_CLIENT_ID` and `WORKOS_API_KEY` can be found in your
        [WorkOS Dashboard](https://dashboard.workos.com/).

        `WORKOS_COOKIE_PASSWORD`: A secure password used to encrypt session cookies. This
        must be at least 32 characters long. You can generate a random one with
        `openssl rand -base64 24`.

        ```env theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        # WorkOS AuthKit Configuration
        WORKOS_CLIENT_ID=client_your_client_id_here
        WORKOS_API_KEY=sk_test_your_api_key_here
        WORKOS_COOKIE_PASSWORD=your_secure_password_here_must_be_at_least_32_characters_long
        WORKOS_REDIRECT_URI=http://localhost:3000/callback

        # Bijection Configuration (you don't have to fill these out, they're generated by Bijection)
        VITE_BIJECTION_URL=https://your-bijection-url.bijection.cloud
        ```
      </Step>

      <Step title="Install AuthKit">
        In a new terminal window, install the AuthKit TanStack Start SDK:

        ```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        npm install @workos/authkit-tanstack-react-start
        ```
      </Step>

      <Step title="Configure Start middleware">
        WorkOS AuthKit requires server-side middleware to manage authentication
        sessions. Update your `src/start.ts` to include the AuthKit middleware:

        ```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { createStart } from '@tanstack/react-start';
        import { authkitMiddleware } from '@workos/authkit-tanstack-react-start';

        export const startInstance = createStart(() => {
          return {
            requestMiddleware: [authkitMiddleware()],
          };
        });
        ```
      </Step>

      <Step title="Configure BijectionProviderWithAuth">
        Update your `src/router.tsx` to wrap the router with `<AuthKitProvider>`
        and `<BijectionProviderWithAuth>`, and provide a custom `useAuthFromAuthKit`
        hook that bridges WorkOS's auth state to Bijection.

        ```tsx {5-6,38-39,41-42,50,52,54,73,75} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { createRouter } from '@tanstack/react-router';
        import { BijectionQueryClient } from '@bijection/react-query';
        import { QueryClient } from '@tanstack/react-query';
        import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query';
        import { BijectionProviderWithAuth, BijectionReactClient } from 'bijection/react';
        import { AuthKitProvider, useAccessToken, useAuth } from '@workos/authkit-tanstack-react-start/client';
        import { useCallback, useMemo } from 'react';
        import { routeTree } from './routeTree.gen';

        export function getRouter() {
          const BIJECTION_URL = (import.meta as any).env.VITE_BIJECTION_URL!;
          if (!BIJECTION_URL) {
            throw new Error('missing VITE_BIJECTION_URL envar');
          }
          const bijection = new BijectionReactClient(BIJECTION_URL);
          const bijectionQueryClient = new BijectionQueryClient(bijection);

          const queryClient = new QueryClient({
            defaultOptions: {
              queries: {
                queryKeyHashFn: bijectionQueryClient.hashFn(),
                queryFn: bijectionQueryClient.queryFn(),
                gcTime: 5000,
              },
            },
          });
          bijectionQueryClient.connect(queryClient);

          const router = createRouter({
            routeTree,
            defaultPreload: 'intent',
            scrollRestoration: true,
            defaultPreloadStaleTime: 0, // Let React Query handle all caching
            defaultErrorComponent: (err) => <p>{err.error.stack}</p>,
            defaultNotFoundComponent: () => <p>not found</p>,
            context: { queryClient, bijectionClient: bijection, bijectionQueryClient },
            Wrap: ({ children }) => (
              <AuthKitProvider>
                <BijectionProviderWithAuth client={bijectionQueryClient.bijectionClient} useAuth={useAuthFromAuthKit}>
                  {children}
                </BijectionProviderWithAuth>
              </AuthKitProvider>
            ),
          });
          setupRouterSsrQueryIntegration({ router, queryClient });

          return router;
        }

        function useAuthFromAuthKit() {
          const { loading, user } = useAuth();
          const { getAccessToken, refresh } = useAccessToken();

          const fetchAccessToken = useCallback(
            async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
              if (!user) {
                return null;
              }

              if (forceRefreshToken) {
                return (await refresh()) ?? null;
              }

              return (await getAccessToken()) ?? null;
            },
            [user, refresh, getAccessToken],
          );

          return useMemo(
            () => ({
              isLoading: loading,
              isAuthenticated: !!user,
              fetchAccessToken,
            }),
            [loading, user, fetchAccessToken],
          );
        }
        ```
      </Step>

      <Step title="Add SSR auth in the root route">
        To make authenticated Bijection queries work during server-side rendering,
        call WorkOS's `getAuth()` in `beforeLoad` and pass the access token to the
        Bijection client.

        ```tsx {34-44} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { HeadContent, Outlet, Scripts, createRootRouteWithContext } from '@tanstack/react-router';
        import { getAuth } from '@workos/authkit-tanstack-react-start';
        import appCssUrl from '../app.css?url';
        import type { QueryClient } from '@tanstack/react-query';
        import type { ReactNode } from 'react';
        import type { BijectionReactClient } from 'bijection/react';
        import type { BijectionQueryClient } from '@bijection/react-query';

        export const Route = createRootRouteWithContext<{
          queryClient: QueryClient;
          bijectionClient: BijectionReactClient;
          bijectionQueryClient: BijectionQueryClient<BijectionReactClient>;
        }>()({
          head: () => ({
            meta: [
              {
                charSet: 'utf-8',
              },
              {
                name: 'viewport',
                content: 'width=device-width, initial-scale=1',
              },
              {
                title: 'Bijection + TanStack Start + WorkOS AuthKit',
              },
            ],
            links: [
              { rel: 'stylesheet', href: appCssUrl },
              { rel: 'icon', href: '/bijection.svg' },
            ],
          }),
          component: RootComponent,
          notFoundComponent: () => <div>Not Found</div>,
          beforeLoad: async (ctx) => {
            const auth = await getAuth();

            // During SSR only (the only time serverHttpClient exists),
            // set the WorkOS auth token to make HTTP queries with.
            if (auth.user) {
              ctx.context.bijectionQueryClient.serverHttpClient?.setAuth(auth.accessToken);
            }

            return { user: auth.user };
          },
        });

        function RootComponent() {
          return (
            <RootDocument>
              <Outlet />
            </RootDocument>
          );
        }

        function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
          return (
            <html lang="en">
              <head>
                <HeadContent />
              </head>
              <body>
                {children}
                <Scripts />
              </body>
            </html>
          );
        }
        ```
      </Step>

      <Step title="Add callback route">
        Unlike the React SPA integration, TanStack Start uses server-side
        authentication which requires an explicit callback route to handle the
        OAuth redirect from WorkOS.

        ```tsx {2,7} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { createFileRoute } from '@tanstack/react-router';
        import { handleCallbackRoute } from '@workos/authkit-tanstack-react-start';

        export const Route = createFileRoute('/callback')({
          server: {
            handlers: {
              GET: handleCallbackRoute(),
            },
          },
        });
        ```
      </Step>

      <Step title="Add sign-in and sign-up redirect routes">
        Create dedicated server routes that call `getSignInUrl()` / `getSignUpUrl()`
        and redirect. Link to these routes from your UI.

        ```tsx {2,9,12} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { createFileRoute } from '@tanstack/react-router';
        import { getSignInUrl } from '@workos/authkit-tanstack-react-start';

        export const Route = createFileRoute('/sign-in')({
          server: {
            handlers: {
              GET: async ({ request }: { request: Request }) => {
                const returnPathname = new URL(request.url).searchParams.get('returnPathname');
                const url = await getSignInUrl(returnPathname ? { data: { returnPathname } } : undefined);
                return new Response(null, {
                  status: 307,
                  headers: { Location: url },
                });
              },
            },
          },
        });
        ```

        ```tsx {2,9,12} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { createFileRoute } from '@tanstack/react-router';
        import { getSignUpUrl } from '@workos/authkit-tanstack-react-start';

        export const Route = createFileRoute('/sign-up')({
          server: {
            handlers: {
              GET: async ({ request }: { request: Request }) => {
                const returnPathname = new URL(request.url).searchParams.get('returnPathname');
                const url = await getSignUpUrl(returnPathname ? { data: { returnPathname } } : undefined);
                return new Response(null, {
                  status: 307,
                  headers: { Location: url },
                });
              },
            },
          },
        });
        ```
      </Step>

      <Step title="Show UI based on authentication state">
        You can control which UI is shown when the user is signed in or signed out using
        Bijection's `<Authenticated>`, `<Unauthenticated>`, `<AuthLoading>` and `<AuthRefreshing>` helper components.

        In TanStack Start, you can use WorkOS's server-side `getAuth()` in a
        route loader to get the user before the page renders.

        <Tip>
          If you choose to build your own auth-integrated components without using the helpers,
          it's important to use the [`useBijectionAuth()`](/api/modules/react#usebijectionauth) hook
          instead of AuthKit's `useAuth()` hook when you need to check whether the user is logged
          in or not. The `useBijectionAuth()` hook makes sure that the browser has fetched the auth
          token needed to make authenticated requests to your Bijection backend, and that the
          Bijection backend has validated it.
        </Tip>

        ```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { createFileRoute } from '@tanstack/react-router';
        import { Authenticated, Unauthenticated } from 'bijection/react';
        import { useAuth } from '@workos/authkit-tanstack-react-start/client';
        import { getAuth } from '@workos/authkit-tanstack-react-start';
        import { bijectionQuery } from '@bijection/react-query';
        import { useSuspenseQuery } from '@tanstack/react-query';
        import { api } from '../../bijection/_generated/api';

        export const Route = createFileRoute('/')({
          component: Home,
          loader: async () => {
            const { user } = await getAuth();
            return { user };
          },
        });

        function Home() {
          const { user } = Route.useLoaderData();
          const { signOut } = useAuth();

          return (
            <div className="p-4">
              <div className="flex justify-between items-center mb-4">
                <h1>Bijection + TanStack Start + WorkOS</h1>
                {user ? (
                  <button onClick={() => signOut()}>Sign out</button>
                ) : (
                  <div className="flex gap-2">
                    <a href="/sign-in">
                      <button>Sign in</button>
                    </a>
                    <a href="/sign-up">
                      <button>Sign up</button>
                    </a>
                  </div>
                )}
              </div>
              <Authenticated>
                <Content />
              </Authenticated>
              <Unauthenticated>
                <p>Please sign in to view data</p>
              </Unauthenticated>
            </div>
          );
        }

        function Content() {
          const { data } = useSuspenseQuery(
            bijectionQuery(api.myFunctions.listNumbers, { count: 10 }),
          );

          return (
            <div>
              <p>Welcome {data.viewer}!</p>
              <p>Numbers: {data.numbers?.join(', ') || 'None'}</p>
            </div>
          );
        }
        ```
      </Step>

      <Step title="Use authentication state in your Bijection functions">
        If the client is authenticated, you can access the information
        stored in the JWT via `ctx.auth.getUserIdentity`.

        If the client isn't authenticated, `ctx.auth.getUserIdentity` will return `null`.

        **Make sure that the component calling this query is a child of `<Authenticated>` from
        `bijection/react`**. Otherwise, it will throw on page load.

        ```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
        import { v } from "bijection/values";
        import { query } from "./_generated/server";

        export const listNumbers = query({
          args: {
            count: v.number(),
          },
          handler: async (ctx, args) => {
            const identity = await ctx.auth.getUserIdentity();
            if (identity === null) {
              throw new Error("Not authenticated");
            }
            const numbers = await ctx.db
              .query("numbers")
              // Ordered by _creationTime, return most recent
              .order("desc")
              .take(args.count);
            return {
              viewer: identity.name,
              numbers: numbers.reverse().map((number) => number.value),
            };
          },
        });
        ```
      </Step>
    </Steps>

    **Note:** The
    TanStack Start template
    includes additional features and functions for a complete working application.
    This tutorial covers the core integration steps, but the template provides a
    more comprehensive implementation.
  </Tab>
</Tabs>

## Next steps

Now that your app is up and running on Bijection and AutKit, refer to the
[main docs](/auth/authkit/index#next-steps) to learn about additional functionality.
