convex-auth

SKILL.md

Convex Auth

Complete guide for authentication and authorization in Convex apps using @convex-dev/auth.

Critical Rules

  1. Always validate auth server-side — client-side <Authenticated> is UX only, not security.
  2. Use getAuthUserId(ctx) in every query/mutation that needs auth — never trust the client.
  3. Authenticated !== Authorized — always check permissions after confirming identity.
  4. Use expo-secure-store for React Native token storage — never AsyncStorage.
  5. Polyfills are required for React Native — import them before any other code in _layout.tsx.
  6. Callback URLs use .site not .cloud — format: https://<deployment>.convex.site/api/auth/callback/<provider>.
  7. Separate OAuth apps for development and production environments.
  8. Use ConvexError for auth/authz errors to provide structured error responses.

Table of Contents

For detailed guides, read the reference files:

  • references/setup-and-providers.md — full setup for all frameworks, env vars, schema
  • references/auth-methods.md — OAuth (GitHub/Google/Apple), Magic Links, OTP, Passwords with verification and reset
  • references/authorization.md — RBAC, team auth, RLS, custom mutations, Next.js middleware
  • references/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
GitHub Stars
1
First Seen
6 days ago
Installed on
amp2
cline2
openclaw2
opencode2
cursor2
kimi-cli2