Skip to main content
Convex provides built-in authentication that works with any OpenID Connect (OIDC) provider or custom JWT tokens. Configure authentication in auth.config.ts and access user identity in your functions.

Configuration

Define your authentication providers in auth.config.ts:
import { AuthConfig } from "convex/server";

export default {
  providers: [
    {
      domain: "https://your.issuer.url.com",
      applicationID: "your-application-id",
    },
  ],
} satisfies AuthConfig;

Auth config

The AuthConfig type defines authentication configuration:
providers
AuthProvider[]
An array of authentication providers allowed to issue JWTs for your app.

Auth providers

Convex supports two types of authentication providers:

OIDC provider

{
  domain: "https://auth.example.com",
  applicationID: "your-app-id",
}
domain
string
The domain of the OIDC auth provider.
applicationID
string
Tokens issued by the auth provider must have this application ID in their audiences.

Custom JWT provider

{
  type: "customJwt",
  issuer: "https://auth.example.com",
  jwks: "https://auth.example.com/.well-known/jwks.json",
  algorithm: "RS256",
  applicationID: "your-app-id", // Optional but recommended
}
type
'customJwt'
Indicates this is a custom JWT provider.
issuer
string
The issuer of the JWT auth provider (e.g., https://auth.example.com).
jwks
string
The URL to fetch the JWKS (JSON Web Key Set) for token verification.
algorithm
'RS256' | 'ES256'
The algorithm used to sign JWT tokens. Convex currently supports RS256 and ES256.
applicationID
string
Tokens must have this application ID in their audiences. Warning: Omitting applicationID is often insecure.

User identity

Access authenticated user information via ctx.auth in queries, mutations, and actions.

Get user identity

import { query } from "./_generated/server";

export const getCurrentUser = query({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) {
      return null; // User not authenticated
    }

    // Identity contains user information from JWT
    const { tokenIdentifier, subject, issuer, email, name } = identity;
    return identity;
  },
});

UserIdentity interface

The UserIdentity object contains information derived from the JWT token. Only tokenIdentifier and issuer are guaranteed to be present - all other fields depend on what the identity provider includes.

Standard fields

These fields are derived from OpenID Connect (OIDC) standard claims:
tokenIdentifier
string
A stable and globally unique string for this identity. No other user, even from a different provider, will have the same string. Derived from JWT claims sub + iss.
subject
string
Identifier for the end-user from the identity provider, not necessarily unique across different providers. JWT claim: sub.
issuer
string
The hostname of the identity provider used to authenticate this user. JWT claim: iss.
name
string
The user’s full name. JWT claim: name.
givenName
string
The user’s given (first) name. JWT claim: given_name.
familyName
string
The user’s family (last) name. JWT claim: family_name.
nickname
string
The user’s nickname or username. JWT claim: nickname.
preferredUsername
string
The user’s preferred username. JWT claim: preferred_username.
profileUrl
string
URL of the user’s profile page. JWT claim: profile.
pictureUrl
string
URL of the user’s profile picture. JWT claim: picture.
email
string
The user’s email address. JWT claim: email.
emailVerified
boolean
Whether the email address has been verified. JWT claim: email_verified.
gender
string
The user’s gender. JWT claim: gender.
birthday
string
The user’s birthday. JWT claim: birthdate.
timezone
string
The user’s timezone. JWT claim: zoneinfo.
language
string
The user’s preferred language. JWT claim: locale.
phoneNumber
string
The user’s phone number. JWT claim: phone_number.
phoneNumberVerified
boolean
Whether the phone number has been verified. JWT claim: phone_number_verified.
address
string
The user’s address. JWT claim: address.
updatedAt
string
When the user’s information was last updated. JWT claim: updated_at.

Custom claims

Any additional custom claims from your JWT are also available. Type assert them if you know their type:
const identity = await ctx.auth.getUserIdentity();
if (identity === null) {
  return null;
}
// Type assert custom claims:
const customClaim = identity.custom_claim as string;
const role = identity.role as "admin" | "user";

Auth interface

The Auth interface is available as ctx.auth in all Convex functions:

getUserIdentity

Get details about the currently authenticated user:
const identity = await ctx.auth.getUserIdentity();
Returns: A UserIdentity object if the Convex client was configured with a valid ID token, otherwise:
  • Returns null in queries, mutations, and actions
  • Throws in HTTP actions

Common patterns

Require authentication

Throw an error if the user is not authenticated:
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const createPost = mutation({
  args: { title: v.string(), content: v.string() },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("Unauthorized: must be logged in to create posts");
    }

    return await ctx.db.insert("posts", {
      title: args.title,
      content: args.content,
      authorId: identity.tokenIdentifier,
    });
  },
});
Store the tokenIdentifier to link documents to users:
// Create a user document on first login:
export const ensureUser = mutation({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("Not authenticated");
    }

    // Check if user exists
    const existingUser = await ctx.db
      .query("users")
      .withIndex("by_token", (q) => q.eq("tokenIdentifier", identity.tokenIdentifier))
      .unique();

    if (existingUser) {
      return existingUser._id;
    }

    // Create new user
    return await ctx.db.insert("users", {
      tokenIdentifier: identity.tokenIdentifier,
      email: identity.email,
      name: identity.name,
    });
  },
});

Role-based access control

Implement custom roles using custom JWT claims:
export const adminOnly = mutation({
  args: { /* ... */ },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("Not authenticated");
    }

    const role = identity.role as string | undefined;
    if (role !== "admin") {
      throw new Error("Unauthorized: admin access required");
    }

    // Proceed with admin operation
  },
});

Best practices

  • Always check authentication when required - getUserIdentity() can return null.
  • Use tokenIdentifier for user linking - It’s stable and globally unique.
  • Configure applicationID - Omitting it in custom JWT providers is often insecure.
  • Store minimal user data - Only store what you need from the identity in your database.
  • Use internal mutations for admin operations - Don’t expose sensitive operations as public functions.

Build docs developers (and LLMs) love