Loading skill
Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Set up Convex authentication with proper user management, identity mapping, and access control patterns. Use when implementing auth flows.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 114% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 148% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 134% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 138% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 217% | 0% |
Implement secure authentication in Convex with user management and access control.
Convex authentication has two main parts:
typescript// convex/schema.ts import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export default defineSchema({ users: defineTable({ // From auth provider identity tokenIdentifier: v.string(), // Unique per auth provider // User profile data name: v.string(), email: v.string(), pictureUrl: v.optional(v.string()), // Your app-specific fields role: v.union( v.literal("user"), v.literal("admin") ), createdAt: v.number(), updatedAt: v.optional(v.number()), }) .index("by_token", ["tokenIdentifier"]) .index("by_email", ["email"]), });
typescript// convex/lib/auth.ts import { QueryCtx, MutationCtx } from "./_generated/server"; import { Doc } from "./_generated/dataModel"; export async function getCurrentUser( ctx: QueryCtx | MutationCtx ): Promise<Doc<"users">> { const identity = await ctx.auth.getUserIdentity(); if (!identity) { throw new Error("Not authenticated"); } const user = await ctx.db .query("users") .withIndex("by_token", q => q.eq("tokenIdentifier", identity.tokenIdentifier) ) .unique(); if (!user) { throw new Error("User not found"); } return user; } export async function getCurrentUserOrNull( ctx: QueryCtx | MutationCtx ): Promise<Doc<"users"> | null> { const identity = await ctx.auth.getUserIdentity(); if (!identity) { return null; } return await ctx.db .query("users") .withIndex("by_token", q => q.eq("tokenIdentifier", identity.tokenIdentifier) ) .unique(); }
typescriptexport async function requireAdmin( ctx: QueryCtx | MutationCtx ): Promise<Doc<"users">> { const user = await getCurrentUser(ctx); if (user.role !== "admin") { throw new Error("Admin access required"); } return user; }
typescript// convex/users.ts import { mutation } from "./_generated/server"; import { v } from "convex/values"; export const storeUser = 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) { // Update last seen or other fields await ctx.db.patch(existingUser._id, { updatedAt: Date.now(), }); return existingUser._id; } // Create new user const userId = await ctx.db.insert("users", { tokenIdentifier: identity.tokenIdentifier, name: identity.name ?? "Anonymous", email: identity.email ?? "", pictureUrl: identity.pictureUrl, role: "user", createdAt: Date.now(), }); return userId; }, });
typescriptimport { mutation } from "./_generated/server"; import { v } from "convex/values"; import { getCurrentUser } from "./lib/auth"; export const updateProfile = mutation({ args: { name: v.string(), }, handler: async (ctx, args) => { const user = await getCurrentUser(ctx); await ctx.db.patch(user._id, { name: args.name, updatedAt: Date.now(), }); }, });
typescriptexport const deleteTask = mutation({ args: { taskId: v.id("tasks") }, handler: async (ctx, args) => { const user = await getCurrentUser(ctx); const task = await ctx.db.get(args.taskId); if (!task) { throw new Error("Task not found"); } // Check ownership if (task.userId !== user._id) { throw new Error("You can only delete your own tasks"); } await ctx.db.delete(args.taskId); }, });
typescript// Schema includes membership table export default defineSchema({ teams: defineTable({ name: v.string(), ownerId: v.id("users"), }), teamMembers: defineTable({ teamId: v.id("teams"), userId: v.id("users"), role: v.union(v.literal("owner"), v.literal("member")), }) .index("by_team", ["teamId"]) .index("by_user", ["userId"]) .index("by_team_and_user", ["teamId", "userId"]), }); // Helper to check team access async function requireTeamAccess( ctx: MutationCtx, teamId: Id<"teams"> ): Promise<{ user: Doc<"users">, membership: Doc<"teamMembers"> }> { const user = await getCurrentUser(ctx); const membership = await ctx.db .query("teamMembers") .withIndex("by_team_and_user", q => q.eq("teamId", teamId).eq("userId", user._id) ) .unique(); if (!membership) { throw new Error("You don't have access to this team"); } return { user, membership }; } // Use in functions export const createProject = mutation({ args: { teamId: v.id("teams"), name: v.string(), }, handler: async (ctx, args) => { await requireTeamAccess(ctx, args.teamId); return await ctx.db.insert("projects", { teamId: args.teamId, name: args.name, }); }, });
typescriptexport const listPublicPosts = query({ args: {}, handler: async (ctx) => { // No auth check - anyone can read return await ctx.db .query("posts") .withIndex("by_published", q => q.eq("published", true)) .collect(); }, });
typescriptexport const getMyPosts = query({ args: {}, handler: async (ctx) => { const user = await getCurrentUser(ctx); return await ctx.db .query("posts") .withIndex("by_user", q => q.eq("userId", user._id)) .collect(); }, });
typescriptexport const getPosts = query({ args: {}, handler: async (ctx) => { const user = await getCurrentUserOrNull(ctx); if (user) { // Show all posts including drafts for this user return await ctx.db .query("posts") .withIndex("by_user", q => q.eq("userId", user._id)) .collect(); } else { // Show only public posts for anonymous users return await ctx.db .query("posts") .withIndex("by_published", q => q.eq("published", true)) .collect(); } }, });
WorkOS AuthKit provides a complete authentication solution with minimal setup.
bashnpm install @workos-inc/authkit-react
typescript// src/main.tsx import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react"; import { ConvexReactClient } from "convex/react"; import { ConvexProvider } from "convex/react"; const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL); // Configure Convex to use WorkOS auth convex.setAuth(useAuth); function App() { return ( <AuthKitProvider clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}> <ConvexProvider client={convex}> <YourApp /> </ConvexProvider> </AuthKitProvider> ); }
bashnpm install @workos-inc/authkit-nextjs
typescript// app/layout.tsx import { AuthKitProvider } from "@workos-inc/authkit-nextjs"; import { ConvexClientProvider } from "./ConvexClientProvider"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html> <body> <AuthKitProvider> <ConvexClientProvider> {children} </ConvexClientProvider> </AuthKitProvider> </body> </html> ); }
typescript// app/ConvexClientProvider.tsx "use client"; import { ConvexReactClient } from "convex/react"; import { ConvexProvider } from "convex/react"; import { useAuth } from "@workos-inc/authkit-nextjs"; const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); export function ConvexClientProvider({ children }: { children: React.ReactNode }) { const { getToken } = useAuth(); convex.setAuth(async () => { return await getToken(); }); return <ConvexProvider client={convex}>{children}</ConvexProvider>; }
bash# .env.local (React/Vite) VITE_CONVEX_URL=https://your-deployment.convex.cloud VITE_WORKOS_CLIENT_ID=your_workos_client_id # .env.local (Next.js) NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud NEXT_PUBLIC_WORKOS_CLIENT_ID=your_workos_client_id WORKOS_API_KEY=your_workos_api_key WORKOS_COOKIE_PASSWORD=generate_a_random_32_character_string
typescript// In your app after user signs in import { useMutation } from "convex/react"; import { api } from "../convex/_generated/api"; import { useEffect } from "react"; import { useAuth } from "@workos-inc/authkit-react"; function YourApp() { const { user } = useAuth(); const storeUser = useMutation(api.users.storeUser); useEffect(() => { if (user) { storeUser(); } }, [user, storeUser]); // ... rest of your app }
If you need to use a different provider, see the Convex auth documentation for:
tokenIdentifier indexgetCurrentUser helper functionstoreUser mutation for first sign-inOther measured skills in the registry, with their headline benchmark lift.