convex-auth
SKILL.md
Convex Auth
Complete guide for authentication and authorization in Convex apps using @convex-dev/auth.
Critical Rules
- Always validate auth server-side — client-side
<Authenticated>is UX only, not security. - Use
getAuthUserId(ctx)in every query/mutation that needs auth — never trust the client. - Authenticated !== Authorized — always check permissions after confirming identity.
- Use
expo-secure-storefor React Native token storage — neverAsyncStorage. - Polyfills are required for React Native — import them before any other code in
_layout.tsx. - Callback URLs use
.sitenot.cloud— format:https://<deployment>.convex.site/api/auth/callback/<provider>. - Separate OAuth apps for development and production environments.
- Use
ConvexErrorfor auth/authz errors to provide structured error responses.
Table of Contents
- Setup — installation and initial configuration
- Framework Providers — React, Next.js, React Native
- Authentication Methods — OAuth, Magic Links, OTP, Passwords
- Authorization Patterns — inline checks, helpers, RBAC, teams
For detailed guides, read the reference files:
references/setup-and-providers.md— full setup for all frameworks, env vars, schemareferences/auth-methods.md— OAuth (GitHub/Google/Apple), Magic Links, OTP, Passwords with verification and resetreferences/authorization.md— RBAC, team auth, RLS, custom mutations, Next.js middlewarereferences/react-native.md— polyfills, SecureStore, OAuth in Expo, step-based auth screen
Setup
Install
npm install @convex-dev/auth @auth/core
npx @convex-dev/auth
npx convex env set SITE_URL http://localhost:5173
Schema
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { authTables } from "@convex-dev/auth/server";
import { v } from "convex/values";
export default defineSchema({
...authTables,
// Your custom tables here
});
Auth Config
// convex/auth.ts
import { convexAuth } from "@convex-dev/auth/server";
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [
// Add providers here
],
});
HTTP Routes
// convex/http.ts
import { httpRouter } from "convex/server";
import { auth } from "./auth";
const http = httpRouter();
auth.addHttpRoutes(http);
export default http;
Framework Providers
React (Vite)
import { ConvexAuthProvider } from "@convex-dev/auth/react";
import { ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
root.render(
<ConvexAuthProvider client={convex}>
<App />
</ConvexAuthProvider>
);
Next.js
// app/layout.tsx
import { ConvexAuthNextjsServerProvider } from "@convex-dev/auth/nextjs/server";
import { ConvexAuthNextjsProvider } from "@convex-dev/auth/nextjs";
export default function RootLayout({ children }) {
return (
<ConvexAuthNextjsServerProvider>
<ConvexAuthNextjsProvider client={convex}>
{children}
</ConvexAuthNextjsProvider>
</ConvexAuthNextjsServerProvider>
);
}
Read references/setup-and-providers.md for Next.js middleware and server component auth.
React Native
// app/_layout.tsx — import polyfills FIRST!
import "../polyfills";
import { ConvexAuthProvider } from "@convex-dev/auth/react";
import * as SecureStore from "expo-secure-store";
import { Platform } from "react-native";
const secureStorage = {
getItem: SecureStore.getItemAsync,
setItem: SecureStore.setItemAsync,
removeItem: SecureStore.deleteItemAsync,
};
<ConvexAuthProvider
client={convex}
storage={Platform.OS !== "web" ? secureStorage : undefined}
>
Read references/react-native.md for polyfills, OAuth, and complete auth screen implementation.
Authentication Methods
OAuth (Quick Start)
// convex/auth.ts
import GitHub from "@auth/core/providers/github";
import { convexAuth } from "@convex-dev/auth/server";
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [GitHub],
});
// Client
const { signIn } = useAuthActions();
<button onClick={() => void signIn("github")}>Sign in with GitHub</button>
Env vars: AUTH_GITHUB_ID, AUTH_GITHUB_SECRET
Callback: https://<deployment>.convex.site/api/auth/callback/github
Passwords with Verification & Reset
// convex/auth.ts
import { Password } from "@convex-dev/auth/providers/Password";
import { ResendOTP } from "./ResendOTP";
import { ResendOTPPasswordReset } from "./ResendOTPPasswordReset";
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [
Password({ verify: ResendOTP, reset: ResendOTPPasswordReset }),
],
});
Read references/auth-methods.md for OTP provider setup, Magic Links, Google, Apple, and sign-in forms.
Authorization Patterns
Inline Auth Check
import { getAuthUserId } from "@convex-dev/auth/server";
export const myQuery = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
// proceed with userId
},
});
Reusable User Mutation Helper
// convex/lib/userMutation.ts
import { customMutation } from "convex-helpers/server/customFunctions";
import { getAuthUserId } from "@convex-dev/auth/server";
import { mutation } from "../_generated/server";
export const userMutation = customMutation(mutation, {
args: {},
input: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Unauthenticated");
const user = await ctx.db.get(userId);
if (!user) throw new Error("User not found");
return { ctx: { user }, args };
},
});
Frontend Auth State
import { Authenticated, Unauthenticated, AuthLoading } from "convex/react";
<AuthLoading><Loading /></AuthLoading>
<Unauthenticated><SignIn /></Unauthenticated>
<Authenticated><Dashboard /></Authenticated>
Read references/authorization.md for RBAC, team/org authorization, RLS, custom JWT claims, and Next.js server-side auth.
Debugging Quick Reference
| Issue | Check |
|---|---|
| "window.addEventListener is not a function" | Add React Native polyfills before all imports |
| OAuth redirect fails | Callback URL uses .site not .cloud; matches provider config exactly |
| Email not sending | Verify AUTH_RESEND_KEY; check domain verification; check spam |
| Session not persisting (RN) | Use expo-secure-store, not AsyncStorage |
| "Invalid client" | Check AUTH_*_ID and AUTH_*_SECRET env vars |
| Auth works but data access fails | You checked authentication but not authorization — add ownership/role checks |
Environment Variables
| Variable | Required For |
|---|---|
SITE_URL |
OAuth, Magic Links |
JWT_PRIVATE_KEY / JWKS |
All methods (auto-generated) |
AUTH_GITHUB_ID / AUTH_GITHUB_SECRET |
GitHub OAuth |
AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET |
Google OAuth |
AUTH_APPLE_ID / AUTH_APPLE_SECRET |
Apple Sign In |
AUTH_RESEND_KEY |
Email (OTP, Magic Links, verification) |
Weekly Installs
2
Repository
imfa-solutions/skillsGitHub Stars
1
First Seen
6 days ago
Security Audits
Installed on
amp2
cline2
openclaw2
opencode2
cursor2
kimi-cli2