convex-workos

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Convex + WorkOS AuthKit

Convex + WorkOS AuthKit

Provider-specific patterns for integrating WorkOS AuthKit with Convex.
适用于 Convex 与 WorkOS AuthKit 集成的专属提供商模式。

Required Configuration

必要配置

1. auth.config.ts

1. auth.config.ts

typescript
// convex/auth.config.ts
const clientId = process.env.WORKOS_CLIENT_ID;

export default {
  providers: [
    {
      type: 'customJwt',
      issuer: 'https://api.workos.com/',
      algorithm: 'RS256',
      applicationID: clientId,
      jwks: `https://api.workos.com/sso/jwks/${clientId}`
    },
    {
      type: 'customJwt',
      issuer: `https://api.workos.com/user_management/${clientId}`,
      algorithm: 'RS256',
      jwks: `https://api.workos.com/sso/jwks/${clientId}`
    }
  ]
};
Note: WorkOS requires TWO provider entries for different JWT issuers.
typescript
// convex/auth.config.ts
const clientId = process.env.WORKOS_CLIENT_ID;

export default {
  providers: [
    {
      type: 'customJwt',
      issuer: 'https://api.workos.com/',
      algorithm: 'RS256',
      applicationID: clientId,
      jwks: `https://api.workos.com/sso/jwks/${clientId}`
    },
    {
      type: 'customJwt',
      issuer: `https://api.workos.com/user_management/${clientId}`,
      algorithm: 'RS256',
      jwks: `https://api.workos.com/sso/jwks/${clientId}`
    }
  ]
};
注意: WorkOS 需要两个提供商条目,对应不同的 JWT 颁发者。

2. Environment Variables

2. 环境变量

bash
undefined
bash
undefined

.env.local (Vite/React)

.env.local (Vite/React)

VITE_WORKOS_CLIENT_ID=client_01... VITE_WORKOS_REDIRECT_URI=http://localhost:5173/callback
VITE_WORKOS_CLIENT_ID=client_01... VITE_WORKOS_REDIRECT_URI=http://localhost:5173/callback

.env.local (Next.js)

.env.local (Next.js)

WORKOS_CLIENT_ID=client_01... WORKOS_API_KEY=sk_test_... WORKOS_COOKIE_PASSWORD=your_32_char_minimum_password_here NEXT_PUBLIC_WORKOS_REDIRECT_URI=http://localhost:3000/callback
WORKOS_CLIENT_ID=client_01... WORKOS_API_KEY=sk_test_... WORKOS_COOKIE_PASSWORD=your_32_char_minimum_password_here NEXT_PUBLIC_WORKOS_REDIRECT_URI=http://localhost:3000/callback

Convex Dashboard Environment Variables

Convex 控制台环境变量

WORKOS_CLIENT_ID=client_01...
undefined
WORKOS_CLIENT_ID=client_01...
undefined

Client Setup

客户端设置

React (Vite)

React(Vite)

typescript
// src/main.tsx
import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react";
import { ConvexProviderWithAuthKit } from "@convex-dev/workos";
import { ConvexReactClient } from "convex/react";

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

ReactDOM.createRoot(document.getElementById("root")!).render(
  <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>
);
Install:
npm install @workos-inc/authkit-react @convex-dev/workos
typescript
// src/main.tsx
import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react";
import { ConvexProviderWithAuthKit } from "@convex-dev/workos";
import { ConvexReactClient } from "convex/react";

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

ReactDOM.createRoot(document.getElementById("root")!).render(
  <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>
);
安装命令:
npm install @workos-inc/authkit-react @convex-dev/workos

Next.js App Router

Next.js App 路由

typescript
// components/ConvexClientProvider.tsx
'use client';

import { ReactNode, useCallback, useRef } from 'react';
import { ConvexReactClient, ConvexProviderWithAuth } from 'convex/react';
import { AuthKitProvider, useAuth, useAccessToken } from '@workos-inc/authkit-nextjs/components';

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
  return (
    <AuthKitProvider>
      <ConvexProviderWithAuth client={convex} useAuth={useAuthFromAuthKit}>
        {children}
      </ConvexProviderWithAuth>
    </AuthKitProvider>
  );
}

function useAuthFromAuthKit() {
  const { user, loading: isLoading } = useAuth();
  const { accessToken, loading: tokenLoading, error: tokenError } = useAccessToken();

  const loading = (isLoading ?? false) || (tokenLoading ?? false);
  const authenticated = !!user && !!accessToken && !loading;

  const stableAccessToken = useRef<string | null>(null);
  if (accessToken && !tokenError) {
    stableAccessToken.current = accessToken;
  }

  const fetchAccessToken = useCallback(async () => {
    if (stableAccessToken.current && !tokenError) {
      return stableAccessToken.current;
    }
    return null;
  }, [tokenError]);

  return {
    isLoading: loading,
    isAuthenticated: authenticated,
    fetchAccessToken,
  };
}
Install:
npm install @workos-inc/authkit-nextjs @convex-dev/workos
typescript
// components/ConvexClientProvider.tsx
'use client';

import { ReactNode, useCallback, useRef } from 'react';
import { ConvexReactClient, ConvexProviderWithAuth } from 'convex/react';
import { AuthKitProvider, useAuth, useAccessToken } from '@workos-inc/authkit-nextjs/components';

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
  return (
    <AuthKitProvider>
      <ConvexProviderWithAuth client={convex} useAuth={useAuthFromAuthKit}>
        {children}
      </ConvexProviderWithAuth>
    </AuthKitProvider>
  );
}

function useAuthFromAuthKit() {
  const { user, loading: isLoading } = useAuth();
  const { accessToken, loading: tokenLoading, error: tokenError } = useAccessToken();

  const loading = (isLoading ?? false) || (tokenLoading ?? false);
  const authenticated = !!user && !!accessToken && !loading;

  const stableAccessToken = useRef<string | null>(null);
  if (accessToken && !tokenError) {
    stableAccessToken.current = accessToken;
  }

  const fetchAccessToken = useCallback(async () => {
    if (stableAccessToken.current && !tokenError) {
      return stableAccessToken.current;
    }
    return null;
  }, [tokenError]);

  return {
    isLoading: loading,
    isAuthenticated: authenticated,
    fetchAccessToken,
  };
}
安装命令:
npm install @workos-inc/authkit-nextjs @convex-dev/workos

Next.js Middleware

Next.js 中间件

typescript
// middleware.ts
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

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

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
};
typescript
// middleware.ts
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

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

export const config = {
  matcher: ['/((?!.*\\\\..*|_next).*)', '/', '/(api|trpc)(.*)']
};

Next.js Auth Routes

Next.js 认证路由

typescript
// app/callback/route.ts
import { handleAuth } from '@workos-inc/authkit-nextjs';
export const GET = handleAuth();

// app/sign-in/route.ts
import { redirect } from 'next/navigation';
import { getSignInUrl } from '@workos-inc/authkit-nextjs';
export async function GET() {
  return redirect(await getSignInUrl());
}

// app/sign-up/route.ts
import { redirect } from 'next/navigation';
import { getSignUpUrl } from '@workos-inc/authkit-nextjs';
export async function GET() {
  return redirect(await getSignUpUrl());
}
typescript
// app/callback/route.ts
import { handleAuth } from '@workos-inc/authkit-nextjs';
export const GET = handleAuth();

// app/sign-in/route.ts
import { redirect } from 'next/navigation';
import { getSignInUrl } from '@workos-inc/authkit-nextjs';
export async function GET() {
  return redirect(await getSignInUrl());
}

// app/sign-up/route.ts
import { redirect } from 'next/navigation';
import { getSignUpUrl } from '@workos-inc/authkit-nextjs';
export async function GET() {
  return redirect(await getSignUpUrl());
}

CORS Configuration (React/Vite only)

CORS 配置(仅适用于 React/Vite)

For React apps, configure CORS in WorkOS Dashboard:
  1. Authentication > Sessions > Cross-Origin Resource Sharing (CORS)
  2. Click Manage
  3. Add your dev domain:
    http://localhost:5173
  4. Add your prod domain when deploying
对于 React 应用,需在 WorkOS 控制台配置 CORS:
  1. 进入 Authentication > Sessions > Cross-Origin Resource Sharing (CORS)
  2. 点击 Manage
  3. 添加开发域名:
    http://localhost:5173
  4. 部署到生产环境时添加生产域名

UI Components

UI 组件

typescript
import { useAuth } from "@workos-inc/authkit-react"; // or authkit-nextjs/components
import { Authenticated, Unauthenticated } from "convex/react";

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

  return (
    <>
      <Authenticated>
        <button onClick={() => signOut()}>Sign out</button>
        <Content />
      </Authenticated>
      <Unauthenticated>
        <button onClick={() => signIn()}>Sign in</button>
      </Unauthenticated>
    </>
  );
}
typescript
import { useAuth } from "@workos-inc/authkit-react"; // 或 authkit-nextjs/components
import { Authenticated, Unauthenticated } from "convex/react";

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

  return (
    <>
      <Authenticated>
        <button onClick={() => signOut()}>Sign out</button>
        <Content />
      </Authenticated>
      <Unauthenticated>
        <button onClick={() => signIn()}>Sign in</button>
      </Unauthenticated>
    </>
  );
}

Auto-Provisioning (Development)

自动配置(开发环境)

Convex can auto-create WorkOS environments for development:
  1. Run template:
    npm create convex@latest -- -t react-vite-authkit
  2. Follow prompts to link Convex team with WorkOS
  3. Dev deployments auto-provision WorkOS environments
Configured automatically:
  • Redirect URI
  • CORS origin
  • Local environment variables in
    .env.local
Limitations:
  • Only works for dev deployments
  • Production must be manually configured
Convex 可自动为开发环境创建 WorkOS 环境:
  1. 运行模板命令:
    npm create convex@latest -- -t react-vite-authkit
  2. 按照提示将 Convex 团队与 WorkOS 关联
  3. 开发部署会自动配置 WorkOS 环境
自动配置内容:
  • 重定向 URI
  • CORS 源
  • .env.local
    中的本地环境变量
限制:
  • 仅适用于开发部署
  • 生产环境需手动配置

Dev vs Prod Configuration

开发与生产环境配置对比

EnvironmentAPI KeyRedirect URI
Development
sk_test_...
http://localhost:3000/callback
Production
sk_live_...
https://your-domain.com/callback
Set different WORKOS_CLIENT_ID in Convex Dashboard for dev vs prod deployments.
环境API 密钥重定向 URI
开发环境
sk_test_...
http://localhost:3000/callback
生产环境
sk_live_...
https://your-domain.com/callback
在 Convex 控制台中为开发和生产部署设置不同的 WORKOS_CLIENT_ID。

WorkOS-Specific Troubleshooting

WorkOS 专属问题排查

IssueCauseFix
CORS errorDomain not addedAdd domain in WorkOS Dashboard > Sessions > CORS
Token validation failsWrong issuerCheck BOTH providers in auth.config.ts
Missing
aud
claim
JWT configCheck WorkOS JWT configuration
"Platform not authorized"Workspace unlinkedRun
npx convex integration workos disconnect-team
then
provision-team
问题原因解决方案
CORS 错误未添加域名在 WorkOS 控制台 > Sessions > CORS 中添加域名
令牌验证失败颁发者配置错误检查 auth.config.ts 中的两个提供商条目
缺少
aud
声明
JWT 配置问题检查 WorkOS JWT 配置
"Platform not authorized"(平台未授权)工作区未关联运行
npx convex integration workos disconnect-team
后再执行
provision-team

"Platform not authorized" Error

"平台未授权" 错误

bash
npx convex integration workos disconnect-team
npx convex integration workos provision-team
Note: Use a different email if creating new WorkOS workspace.
bash
npx convex integration workos disconnect-team
npx convex integration workos provision-team
注意:创建新的 WorkOS 工作区时请使用不同的邮箱。

DO ✅

正确做法 ✅

  • Include BOTH provider entries in auth.config.ts (different issuers)
  • Configure CORS for React/Vite apps
  • Use
    useConvexAuth()
    not WorkOS's
    useAuth()
    for auth state
  • Set WORKOS_CLIENT_ID in Convex Dashboard
  • Use 32+ char WORKOS_COOKIE_PASSWORD for Next.js
  • 在 auth.config.ts 中包含两个提供商条目(对应不同的颁发者)
  • 为 React/Vite 应用配置 CORS
  • 使用
    useConvexAuth()
    而非 WorkOS 的
    useAuth()
    来获取认证状态
  • 在 Convex 控制台中设置 WORKOS_CLIENT_ID
  • 为 Next.js 使用 32 位以上的 WORKOS_COOKIE_PASSWORD

DON'T ❌

错误做法 ❌

  • Forget the second provider entry (user_management issuer)
  • Skip CORS configuration for browser-based apps
  • Use WorkOS auth hooks to gate Convex queries
  • Hardcode the client ID (use env var)
  • Use same WorkOS env for dev and prod
  • 遗漏第二个提供商条目(user_management 颁发者)
  • 跳过浏览器端应用的 CORS 配置
  • 使用 WorkOS 认证钩子来限制 Convex 查询
  • 硬编码客户端 ID(使用环境变量)
  • 为开发和生产环境使用同一个 WorkOS 环境",