Skip to main content

Adding WorkOS AuthKit to an Existing App

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

If you're just getting started with Convex and WorkOS AuthKit, see the Getting Started instructions instead.

Project configuration

The first step to getting your app up and running with WorkOS AuthKit is getting your Convex project properly configured. Most users should opt for using a Managed WorkOS team where Convex 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 Convex application then you should follow the Standard WorkOS team instructions.

  1. Create or update convex.json

    You'll need a convex.json file in the root of your project with contents that match your framework. You can find more details about the authKit section of convex.json in the Automatic Config 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.

    convex.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}"]
    }
    }
    }
    }
  2. Create or update auth.config.ts

    In your app's convex/ folder, create or update the auth.config.ts file with the following code. This is the server-side configuration for validating access tokens.

    convex/auth.config.ts
    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;
  3. Deploy your configuration to your dev environment

    During deployment, you will be prompted to create a new Convex-managed WorkOS team or an existing one will be detected and used. Convex will then provision a new environment for your application in your WorkOS team.

    npx convex dev

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

Client configuration

Convex offers a provider that is specifically for integrating with WorkOS AuthKit called <ConvexProviderWithAuthKit>. It works using WorkOS's 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.

Example: React with Convex and AuthKit

This guide assumes you have AuthKit set up and have a working React app with Convex. If not follow the Convex React Quickstart first. Then:

  1. Set up CORS in the WorkOS Dashboard
    tip

    If you're using a Convex-managed WorkOS team, this was done for you in Project configuration.

    In your WorkOS Dashboard, go to 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.

    Setting up CORS

  2. Set up your environment variables
    tip

    If you're using a Convex-managed WorkOS team, this was done for you in Project configuration.

    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.

    .env.local
    # WorkOS AuthKit Configuration
    VITE_WORKOS_CLIENT_ID=your-workos-client-id-here
    VITE_WORKOS_REDIRECT_URI=http://localhost:5173/callback
  3. Install AuthKit

    In a new terminal window, install the AuthKit React SDK:

    npm install @workos-inc/authkit-react @convex-dev/workos
  4. Configure ConvexProviderWithAuthKit

    AuthKit and Convex both have provider components that provide authentication and client context to your app.

    You should already have <ConvexProvider> wrapping your app. Replace it with <ConvexProviderWithAuthKit>, 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.

    src/main.tsx
    import { StrictMode } from "react";
    import { createRoot } from "react-dom/client";
    import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react";
    import { ConvexReactClient } from "convex/react";
    import { ConvexProviderWithAuthKit } from "@convex-dev/workos";
    import "./index.css";
    import App from "./App.tsx";

    const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);

    createRoot(document.getElementById("root")!).render(
    <StrictMode>
    <AuthKitProvider
    clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}
    redirectUri={import.meta.env.VITE_WORKOS_REDIRECT_URI}
    >
    <ConvexProviderWithAuthKit client={convex} useAuth={useAuth}>
    <App />
    </ConvexProviderWithAuthKit>
    </AuthKitProvider>
    </StrictMode>,
    );
  5. Show UI based on authentication state

    You can control which UI is shown when the user is signed in or signed out using Convex's <Authenticated>, <Unauthenticated> and <AuthLoading> 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 Convex 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 useConvexAuth() hook instead of AuthKit's useAuth() hook when you need to check whether the user is logged in or not. The useConvexAuth() hook makes sure that the browser has fetched the auth token needed to make authenticated requests to your Convex backend, and that the Convex backend has validated it.

    src/App.tsx
    import { Authenticated, Unauthenticated, useQuery } from 'convex/react';
    import { api } from '../convex/_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>Convex + 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>
    );
    }
  6. Use authentication state in your Convex 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 convex/react. Otherwise, it will throw on page load.

    convex/myFunctions.ts
    import { v } from "convex/values";
    import { query } from "./_generated/server";

    export const listNumbers = query({
    args: {
    count: v.number(),
    },
    handler: async (ctx, args) => {
    const numbers = await ctx.db
    .query("numbers")
    // Ordered by _creationTime, return most recent
    .order("desc")
    .take(args.count);
    return {
    viewer: (await ctx.auth.getUserIdentity())?.name ?? null,
    numbers: numbers.reverse().map((number) => number.value),
    };
    },
    });

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.

Next steps

Now that your app is up and running on Convex and AutKit, refer to the main docs to learn about additional functionality.