convex-clerk
Scannednpx machina-cli add skill PolarCoding85/convex-agent-skillz/convex-clerk-skill --openclawConvex + Clerk Authentication
Provider-specific patterns for integrating Clerk with Convex.
Required Configuration
1. auth.config.ts
// convex/auth.config.ts
import { AuthConfig } from 'convex/server';
export default {
providers: [
{
domain: process.env.CLERK_JWT_ISSUER_DOMAIN!,
applicationID: 'convex'
}
]
} satisfies AuthConfig;
CRITICAL: JWT template in Clerk MUST be named exactly convex.
2. Environment Variables
# .env.local (Vite)
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
# .env.local (Next.js)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
# Convex Dashboard Environment Variables
CLERK_JWT_ISSUER_DOMAIN=https://verb-noun-00.clerk.accounts.dev
CLERK_WEBHOOK_SECRET=whsec_... # If using webhooks
Client Setup
React (Vite)
// src/main.tsx
import { ClerkProvider, useAuth } from "@clerk/clerk-react";
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
ReactDOM.createRoot(document.getElementById("root")!).render(
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<App />
</ConvexProviderWithClerk>
</ClerkProvider>
);
Next.js App Router
// components/ConvexClientProvider.tsx
'use client';
import { ReactNode } from 'react';
import { ConvexReactClient } from 'convex/react';
import { ConvexProviderWithClerk } from 'convex/react-clerk';
import { useAuth } from '@clerk/nextjs';
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export default function ConvexClientProvider({ children }: { children: ReactNode }) {
return (
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithClerk>
);
}
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';
import ConvexClientProvider from '@/components/ConvexClientProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<ClerkProvider>
<ConvexClientProvider>{children}</ConvexClientProvider>
</ClerkProvider>
</body>
</html>
);
}
Next.js Middleware
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect();
}
});
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
};
UI Components
Use Convex auth components, NOT Clerk's:
// ✅ Correct
import { Authenticated, Unauthenticated, AuthLoading } from 'convex/react';
// ❌ Don't use these for conditional rendering
import { SignedIn, SignedOut } from '@clerk/clerk-react';
import { SignInButton, UserButton } from "@clerk/clerk-react";
import { Authenticated, Unauthenticated } from "convex/react";
function App() {
return (
<>
<Authenticated>
<UserButton />
<Content />
</Authenticated>
<Unauthenticated>
<SignInButton />
</Unauthenticated>
</>
);
}
Clerk Webhooks for User Sync
See WEBHOOKS.md for complete implementation.
Setup in Clerk Dashboard:
- Webhooks > Add Endpoint
- URL:
https://your-deployment.convex.site/clerk-users-webhook - Events: Select all
user.*events - Copy Signing Secret → Convex Dashboard env vars as
CLERK_WEBHOOK_SECRET
Accessing User Info
Client-side (Clerk SDK)
import { useUser } from "@clerk/clerk-react";
function Profile() {
const { user } = useUser();
return <span>Hello, {user?.fullName}</span>;
}
Server-side (Convex functions)
export const myQuery = query({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
// identity.name, identity.email, etc.
// Fields depend on Clerk JWT template claims config
}
});
Dev vs Prod Configuration
| Environment | Publishable Key | Issuer Domain |
|---|---|---|
| Development | pk_test_... | https://verb-noun-00.clerk.accounts.dev |
| Production | pk_live_... | https://clerk.your-domain.com |
Set different values in Convex Dashboard for dev vs prod deployments.
Clerk-Specific Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| Token not generated | JWT template not named "convex" | Rename template to exactly convex |
aud mismatch | Wrong applicationID | Use applicationID: "convex" |
iss mismatch | Wrong domain | Copy Frontend API URL from Clerk |
| Webhook fails | Wrong secret | Copy Signing Secret from Clerk webhook |
DO ✅
- Name JWT template exactly
convex - Use
ConvexProviderWithClerkwithuseAuthfrom Clerk - Use
useConvexAuth()not Clerk'suseAuth()for auth state - Use Convex's
<Authenticated>not Clerk's<SignedIn> - Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard
DON'T ❌
- Rename the JWT template from "convex"
- Use Clerk's auth hooks to gate Convex queries
- Hardcode the issuer domain (use env var)
- Forget to deploy after changing auth.config.ts
Source
git clone https://github.com/PolarCoding85/convex-agent-skillz/blob/main/.claude/skills/convex-clerk-skill/SKILL.mdView on GitHub Overview
Clerk + Convex authentication integration. It helps you configure Clerk as an auth provider, wire ConvexProviderWithClerk, and handle Clerk webhooks for user sync.
How This Skill Works
Configures Convex to trust Clerk JWTs via auth.config.ts, with the issuer domain and applicationID 'convex'. The integration uses ConvexProviderWithClerk (and ClerkProvider where needed) to tie Clerk sessions to Convex clients, and enables Clerk webhooks for user sync and route protection via Clerk middleware.
When to Use It
- Setting up Clerk as the auth provider for a Convex app (React or Next.js).
- Configuring ConvexProviderWithClerk in your frontend (Vite/Next.js) and using Clerk's useAuth.
- Implementing Clerk webhooks for user sync (clerk-users-webhook) and signing secret management.
- Troubleshooting Clerk-specific auth issues, such as JWT template naming or issuer domain mismatches.
- Protecting routes with Clerk middleware in Next.js and ensuring UI uses Convex auth components.
Quick Start
- Step 1: Configure Clerk in auth.config.ts with the Clerk issuer domain and applicationID 'convex'; ensure the JWT template is named convex.
- Step 2: Add environment variables (VITE_CLERK_PUBLISHABLE_KEY / NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, CLERK_JWT_ISSUER_DOMAIN, CLERK_WEBHOOK_SECRET) and set them in your hosting environment.
- Step 3: Wrap your app with ClerkProvider and ConvexProviderWithClerk (and useAuth where needed); use Convex auth components for UI and implement Clerk webhooks if needed.
Best Practices
- Name the Clerk JWT template convex exactly in Clerk to match Convex expectations.
- Set CLERK_JWT_ISSUER_DOMAIN consistently in auth.config.ts and environment variables.
- Store CLERK_SECRET_KEY and publishable keys securely; never commit them to code.
- Wrap your app with ClerkProvider and ConvexProviderWithClerk; use Convex auth components instead of Clerk UI components.
- Enable Clerk webhooks and copy the signing secret to the Convex Dashboard; verify webhook events.
Example Use Cases
- A React (Vite) app integrating ClerkProvider with ConvexProviderWithClerk and useAuth.
- A Next.js App Router setup using ConvexClientProvider and Clerk useAuth for authentication state.
- Next.js middleware configured with clerkMiddleware to protect /dashboard routes.
- Clerk webhooks endpoint at /clerk-users-webhook to sync Clerk users with Convex.
- Troubleshooting a mismatch where the Clerk JWT template name is not convex, causing auth failures.