Skip to content
v3.0.1 — MCP OAuth, organizations, passkeys, SSR adaptersChangelog
Convex Auth
Esc
↑↓navigate↵open⌘Jpreview
On this page

Quickstart

Get convex-auth running in a new Convex app in 10 minutes.

This guide assumes you already installed convex-auth and set JWT_PRIVATE_KEY, JWKS, CONVEX_SITE_URL, and EMAIL_FROM_ADDRESS. If not, start with Installation.

At the end you will have email/password and Google/GitHub/Discord OAuth sign-in working in a React (Vite) app. Building with a different framework? Each has its own guide: Next.js, TanStack Start, Hono, or React Native / Expo.

Project layout

When you are done, your project will look like this:

  • convex/
    • auth.config.ts
      • registers convex-auth as the auth provider
    • convex.config.ts
      • mounts the convexAuth component
    • auth.ts
      • configures convexAuth({...}) and re-exports actions
    • http.ts
      • mounts /api/auth/* routes
  • src/
    • main.tsx
      • wraps the app with ConvexAuthClientProvider
    • SignIn.tsx
      • calls useAuthActions

Steps

Register the auth provider

convex/auth.config.ts tells Convex that convex-auth is your auth provider:

import { createConvexAuthProvider } from "@vortex-api/convex-auth/convex";

export default {
  providers: [createConvexAuthProvider()],
};

Mount the component

convex/convex.config.ts mounts the convex-auth component and wires the key env vars:

import { defineApp } from "convex/server";
import { v } from "convex/values";
import auth from "@vortex-api/convex-auth/convex.config";

const app = defineApp({
  env: {
    JWT_PRIVATE_KEY: v.string(),
    JWKS: v.string(),
  },
});

app.use(auth, {
  env: {
    JWT_PRIVATE_KEY: app.env.JWT_PRIVATE_KEY,
    JWKS: app.env.JWKS,
  },
});

export default app;

Configure auth in convex/auth.ts

Choose which providers are enabled and how they behave:

import { components } from "./_generated/api";
import { convexAuth, type EmailDraft } from "@vortex-api/convex-auth/convex";

const siteUrl = process.env.CONVEX_SITE_URL?.replace(/\/$/, "");

export const auth = convexAuth({
  component: components.convexAuth,
  emailAndPassword: {
    enabled: true,
    email: {
      from: process.env.EMAIL_FROM_ADDRESS ?? "auth@example.com",
      appOrigin: siteUrl,
      sendEmail: async (draft: EmailDraft) => {
        // production: call Resend/Postmark/SES
        console.log("Email draft", draft);
        return "email-id";
      },
      sendOnSignUp: true,
      sendOnSignIn: false,
    },
  },
  oauth: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID ?? "",
      clientSecret: process.env.GITHUB_CLIENT_SECRET ?? "",
    },
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID ?? "",
      clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
    },
    discord: {
      clientId: process.env.DISCORD_CLIENT_ID ?? "",
      clientSecret: process.env.DISCORD_CLIENT_SECRET ?? "",
    },
  },
});

export const {
  signUp,
  signIn,
  signOut,
  updateSession,
  sendEmailVerification,
  verifyEmail,
  sendPasswordReset,
  resetPassword,
  verifyPassword,
  twoFactorEnable,
  twoFactorVerifyTOTP,
  twoFactorVerifyBackupCode,
  twoFactorDisable,
  twoFactorGenerateBackupCodes,
} = auth;

Wire HTTP routes

convex/http.ts exposes the OAuth redirect/callback, email verification, and password reset routes:

import { httpRouter } from "convex/server";
import { auth } from "./auth";

const http = httpRouter();
auth.addHttpRoutes(http);

export default http;

Wrap the React app

src/main.tsx wraps ConvexAuthClientProvider inside ConvexProvider — the auth provider calls useConvex(), so the Convex client context must be above it:

import { ConvexReactClient, ConvexProvider } from "convex/react";
import { ConvexAuthClientProvider } from "@vortex-api/convex-auth/react";
import { api } from "../convex/_generated/api";
import App from "./App";

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

function Root() {
  return (
    <ConvexProvider client={convex}>
      <ConvexAuthClientProvider actions={api.auth}>
        <App />
      </ConvexAuthClientProvider>
    </ConvexProvider>
  );
}

export default Root;

Sign in from a component

import { useAuthActions } from "@vortex-api/convex-auth/react";

export function SignIn() {
  const { signIn, isLoading, isAuthenticated } = useAuthActions();

  async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    await signIn({
      email: formData.get("email") as string,
      password: formData.get("password") as string,
    });
  }

  if (isAuthenticated) {
    return <p>Already signed in.</p>;
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button type="submit" disabled={isLoading}>
        {isLoading ? "Signing in..." : "Sign in"}
      </button>
    </form>
  );
}

Verify and deploy

pnpm dlx @vortex-api/convex-auth check
pnpm dlx @vortex-api/convex-auth preflight
pnpm dlx convex deploy

Email verification and password reset are one-click via the /api/auth/verify-email and /api/auth/reset-password/:token HTTP routes. In production, sendEmail should call Resend, Postmark, SES, or any other email service.

Next steps

Was this page helpful?