Skip to main content
The authentication API allows you to access information about the currently authenticated user in your Convex functions.

Auth context

Auth

The ctx.auth object is available in queries, mutations, and actions.
import { query } from "./_generated/server";

export const getCurrentUser = query({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) {
      return null;
    }
    // identity contains user information from the JWT
    return identity;
  },
});

getUserIdentity

Get details about the currently authenticated user.
const identity = await ctx.auth.getUserIdentity();
return
UserIdentity | null
A UserIdentity object if the user is authenticated, or null if not.
  • In queries, mutations, and actions: returns null if not authenticated
  • In HTTP actions: throws an error if not authenticated

User identity

UserIdentity

Information about an authenticated user, derived from their JWT.
const identity = await ctx.auth.getUserIdentity();
if (identity) {
  console.log(identity.tokenIdentifier); // Stable unique identifier
  console.log(identity.subject);         // User ID from auth provider
  console.log(identity.email);           // Email (if available)
  console.log(identity.name);            // Full name (if available)
}
tokenIdentifier
string
A stable and globally unique identifier for this user. No other user, even from a different identity provider, will have the same identifier.Tip: Use this as your user ID or to look up users in your database.Derived from JWT claims: sub + iss
subject
string
Identifier for the user from the identity provider. Not necessarily unique across different providers.JWT claim: sub
issuer
string
The hostname of the identity provider that authenticated this user.JWT claim: iss
email
string
The user’s email address.JWT claim: email
emailVerified
boolean
Whether the email address has been verified.JWT claim: email_verified
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.JWT claim: nickname
preferredUsername
string
The user’s preferred username.JWT claim: preferred_username
profileUrl
string
URL to the user’s profile page.JWT claim: profile
pictureUrl
string
URL to the user’s profile picture.JWT claim: picture
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
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
address
string
The user’s address.JWT claim: address
updatedAt
string
When the user information was last updated.JWT claim: updated_at

Custom claims

Access custom JWT claims by asserting their type:
const identity = await ctx.auth.getUserIdentity();
if (identity === null) {
  throw new Error("Not authenticated");
}

// Assert the type of a custom claim
const role = identity.role as string;
const permissions = identity.permissions as string[];

Common patterns

Require authentication

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 === null) {
      throw new Error("Not authenticated");
    }
    
    const postId = await ctx.db.insert("posts", {
      title: args.title,
      content: args.content,
      authorId: identity.tokenIdentifier,
    });
    
    return postId;
  },
});

Look up user in database

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

export const getCurrentUser = query({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) {
      return null;
    }
    
    // Look up user by tokenIdentifier
    const user = await ctx.db
      .query("users")
      .withIndex("by_token", (q) => 
        q.eq("tokenIdentifier", identity.tokenIdentifier)
      )
      .unique();
    
    return user;
  },
});

Create user on first login

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

export const storeUser = mutation({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) {
      throw new Error("Not authenticated");
    }
    
    // Check if user already exists
    const existing = await ctx.db
      .query("users")
      .withIndex("by_token", (q) =>
        q.eq("tokenIdentifier", identity.tokenIdentifier)
      )
      .unique();
    
    if (existing) {
      return existing._id;
    }
    
    // Create new user
    const userId = await ctx.db.insert("users", {
      tokenIdentifier: identity.tokenIdentifier,
      name: identity.name ?? "Anonymous",
      email: identity.email,
    });
    
    return userId;
  },
});

Role-based access control

import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const deletePost = mutation({
  args: { postId: v.id("posts") },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (identity === null) {
      throw new Error("Not authenticated");
    }
    
    // Check user role from custom claim
    const role = identity.role as string | undefined;
    
    const post = await ctx.db.get(args.postId);
    if (!post) {
      throw new Error("Post not found");
    }
    
    // Allow if admin or post author
    const isAdmin = role === "admin";
    const isAuthor = post.authorId === identity.tokenIdentifier;
    
    if (!isAdmin && !isAuthor) {
      throw new Error("Permission denied");
    }
    
    await ctx.db.delete(args.postId);
  },
});

Auth configuration

AuthConfig

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

export default {
  providers: [
    {
      domain: "https://your.issuer.url.com",
      applicationID: "your-application-id",
    },
  ],
} satisfies AuthConfig;
providers
AuthProvider[]
required
Array of authentication providers that can issue JWTs for your app.

OIDC provider

{
  domain: "https://accounts.google.com",
  applicationID: "your-google-client-id",
}
domain
string
required
The domain of the OIDC auth provider.
applicationID
string
required
Tokens 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'
required
Identifies this as a custom JWT provider.
issuer
string
required
The issuer of the JWT (e.g., https://auth.example.com).
jwks
string
required
URL to fetch the JWKS (e.g., https://auth.example.com/.well-known/jwks.json).
algorithm
'RS256' | 'ES256'
required
The algorithm used to sign JWT tokens.
applicationID
string
Tokens must have this application ID in their audiences.Warning: Omitting applicationID is often insecure.

Best practices

Always check authentication

For protected operations, always check that getUserIdentity() returns a non-null value.

Use tokenIdentifier as user ID

The tokenIdentifier is stable and unique across all users and providers. Use it to identify users in your database.

Store user data separately

Don’t rely solely on JWT claims. Create a users table and store important user data there.

Validate custom claims

When using custom JWT claims, validate and type-assert them appropriately.

Build docs developers (and LLMs) love