name: clerk-convex-expo-auth description: Complete authentication blueprint for React Native / Expo apps using Clerk + Convex. Covers backend auth config, user schema, webhook pipeline, JWT validation, frontend providers, route protection, sign-in/sign-up flows, email verification, Apple Sign-In, onboarding, and account deletion. Use when building auth for Clerk + Convex + Expo projects, setting up webhooks, or implementing sign-in/sign-up flows.
Clerk + Convex + Expo Authentication
Complete authentication system for React Native / Expo apps using Clerk (identity provider) and Convex (backend). This skill provides a production-ready blueprint covering every layer from JWT validation to account deletion cascades.
When to Use
- Setting up auth in a new Clerk + Convex + Expo project
- Adding sign-in/sign-up flows (email/password, Apple Sign-In)
- Configuring Clerk-to-Convex webhooks for user sync
- Implementing route protection and onboarding flows
- Adding account deletion with cascading cleanup
Tech Stack
| Package | Purpose |
|---------|---------|
| @clerk/clerk-expo | Clerk SDK for React Native/Expo (client-side auth) |
| @clerk/backend | Clerk backend utilities (webhook type definitions) |
| convex | Backend framework (ConvexProviderWithClerk, ctx.auth) |
| svix | Webhook signature verification |
| expo-secure-store | Encrypted token storage on device |
| expo-apple-authentication | Native Apple Sign-In (iOS) |
| expo-auth-session | OAuth session handling |
| expo-web-browser | Opens browser for OAuth flows |
Install:
npx expo install @clerk/clerk-expo expo-secure-store expo-apple-authentication expo-auth-session expo-web-browser
npm install @clerk/backend svix
Environment Variables
CLERK_JWT_ISSUER_DOMAIN=https://<your-clerk-instance>.clerk.accounts.dev
CLERK_WEBHOOK_SECRET=whsec_<your-webhook-secret>
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_<your-publishable-key>
EXPO_PUBLIC_CONVEX_URL=https://<your-convex-deployment>.convex.cloud
Instructions
Architecture Overview
Client (Expo) Backend (Convex)
───────────── ────────────────
ClerkProvider auth.config.ts (JWT validation)
└─ ConvexProviderWithClerk getUser() helper (auth enforcement)
└─ JWT auto-attached Webhook handler (user sync)
to every request Internal mutations (upsert/delete)
Data flows:
- Auth: Client -> Clerk JWT -> Convex validates against
CLERK_JWT_ISSUER_DOMAIN - User sync: Clerk event -> Webhook POST -> Convex HTTP endpoint -> Svix verifies -> Internal mutation
- Per-request:
ctx.auth.getUserIdentity()-> lookup byexternalIdindex -> return user ID
Backend Setup
1. Auth Config (convex/auth.config.ts)
import { AuthConfig } from "convex/server";
export default {
providers: [
{
domain: process.env.CLERK_JWT_ISSUER_DOMAIN!,
applicationID: "convex",
},
],
} satisfies AuthConfig;
2. User Schema
// convex/schema.ts
users: defineTable({
externalId: v.string(), // Clerk user ID (identity.subject)
name: v.optional(v.string()),
nickname: v.optional(v.string()),
email: v.optional(v.string()),
image_url: v.optional(v.string()),
has_image: v.optional(v.boolean()),
status: v.union(v.literal("online"), v.literal("offline")),
})
.index("by_externalId", ["externalId"])
The by_externalId index is critical -- used by both the webhook handler and getUser().
3. Auth Helper (getUser)
Central auth enforcement function. Three usage modes:
| Call | Behavior |
|------|----------|
| getUser(ctx) | Returns user ID or null |
| getUser(ctx, { required: true }) | Returns user ID or throws |
| getUser(ctx, { required: true, createIfMissing: true }) | Returns user ID, creating record on first access |
See references/backend.md for full implementation.
4. Webhook Handler (convex/http.ts)
Receives user.created, user.updated, user.deleted events from Clerk, verifies signatures with Svix, and routes to internal mutations.
See references/webhook-setup.md for step-by-step setup and references/backend.md for implementation.
5. Auth Enforcement Patterns
Every protected endpoint calls getUser() at the top:
// Hard requirement (most mutations)
const userId = await getUser(ctx, { required: true });
// Auto-create (onboarding, first-time actions)
const userId = await getUser(ctx, { required: true, createIfMissing: true });
// Soft check (public queries)
const userId = await getUser(ctx);
if (!userId) return null;
Frontend Setup
1. Auth Provider (components/auth/index.tsx)
Wraps the app with ClerkProvider + ConvexProviderWithClerk. Uses expo-secure-store for token persistence.
See references/frontend.md for full implementation.
2. Route Protection
Two-layer protection:
- Clerk layer:
isSignedInfromuseAuth()or<Authenticated>/<Unauthenticated>fromconvex/react - App layer:
hasCompletedOnboardingConvex query for nickname check
3. Sign-In / Sign-Up
Uses useSignIn() and useSignUp() from @clerk/clerk-expo:
signIn.create({ identifier, password })for sign-insignUp.create({ emailAddress, password })+prepareEmailAddressVerification()for sign-up
4. Email Verification
signUp.attemptEmailAddressVerification({ code }) with 6-digit code.
5. Apple Sign-In (iOS)
Uses useSignInWithApple() from @clerk/clerk-expo + native expo-apple-authentication button.
6. Account Deletion Cascade
clerkUser.delete() -> Clerk fires user.deleted webhook -> Convex cleans up all related data.
Clerk Hook Reference
| Hook | Package | Returns |
|------|---------|---------|
| useAuth() | @clerk/clerk-expo | { isLoaded, isSignedIn, signOut } |
| useUser() | @clerk/clerk-expo | { user } |
| useSignIn() | @clerk/clerk-expo | { signIn, setActive, isLoaded } |
| useSignUp() | @clerk/clerk-expo | { signUp, setActive, isLoaded } |
| useSignInWithApple() | @clerk/clerk-expo | { startAppleAuthenticationFlow } |
| useConvexAuth() | convex/react | { isAuthenticated } |
Common Clerk Error Codes
| Code | Meaning |
|------|---------|
| form_password_incorrect | Wrong password |
| form_identifier_not_found | No account with this email |
| form_param_format_invalid | Invalid email format |
| form_identifier_exists | Email already registered |
| form_password_pwned | Password in breached database |
| form_password_length_too_short | Password too short |
Security Summary
| Layer | Mechanism |
|-------|-----------|
| Client token storage | expo-secure-store (encrypted) |
| Client-to-backend auth | JWT auto-attached by ConvexProviderWithClerk |
| Backend JWT validation | Convex validates against CLERK_JWT_ISSUER_DOMAIN |
| Webhook security | svix verifies signatures with CLERK_WEBHOOK_SECRET |
| Per-endpoint authorization | getUser() at the top of every mutation/query |
| Internal mutations | internalMutation prevents client access |
| Account deletion | Cascading cleanup via webhook |
| Token refresh | Handled automatically by Clerk SDK |
Gotchas
- Webhook URL uses
.convex.site(NOT.convex.cloud) - There's a ~1s delay between Clerk action and webhook arrival --
createIfMissingingetUser()acts as fallback - Clerk retries failed webhooks with exponential backoff automatically
v.any() as Validator<UserJSON>skips runtime validation but gives correct TypeScript types- On
user.updated, only patch Clerk-managed fields -- never overwrite app-specific fields likenickname
Reference Files
references/backend.md- Full backend implementation (getUser, user mutations, onboarding)references/frontend.md- Full frontend implementation (providers, forms, Apple Sign-In, modals)references/webhook-setup.md- Step-by-step webhook configuration guide
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.