Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
waynesutton avatar

Convex Setup Auth

  • 3 installs
  • 627 repo stars
  • Updated May 20, 2026
  • waynesutton/markdown-site

Sets up Convex authentication with user management, identity mapping, and access control, choosing among Convex Auth, Clerk, WorkOS, Auth0, or custom JWT.

About

This skill implements secure Convex authentication with a users table, identity mapping, auth helpers, and role-based access control. A developer uses it when adding auth for the first time, wiring OAuth providers, or adding access control to a Convex app.

  • Requires choosing the auth provider before writing setup code
  • Covers user management, identity mapping, and role-based access

Convex Setup Auth by the numbers

  • 3 all-time installs (skills.sh)
  • +1 installs in the week ending Aug 5, 2026 (Skillselion tracking)
  • Ranked #3,739 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/waynesutton/markdown-site --skill convex-setup-auth

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs3
repo stars627
Last updatedMay 20, 2026
Repositorywaynesutton/markdown-site

What it does

Sets up Convex authentication with user management, identity mapping, and access control, choosing among Convex Auth, Clerk, WorkOS, Auth0, or custom JWT.

Files

SKILL.mdMarkdownGitHub ↗

Convex Authentication Setup

Implement secure authentication in Convex with user management and access control.

When to Use

  • Setting up authentication for the first time
  • Implementing user management (users table, identity mapping)
  • Creating authentication helper functions
  • Setting up auth providers (Convex Auth, Clerk, WorkOS AuthKit, Auth0, custom JWT)

First Step: Choose the Auth Provider

Do not assume a provider. Before writing setup code:

1. Ask the user which auth solution they want, unless the repository already makes it obvious 2. If the repo already uses a provider, continue with that provider unless the user wants to switch 3. If the user has not chosen and the repo does not make it obvious, ask before proceeding

Common options:

  • Convex Auth: good default when the user wants auth handled directly in Convex
  • Clerk: use when the app already uses Clerk
  • WorkOS AuthKit: use when the app already uses WorkOS
  • Auth0: use when the app already uses Auth0
  • Custom JWT provider: use when integrating an existing auth system

Look for signals in the repo before asking:

  • Dependencies such as @clerk/*, @workos-inc/*, @auth0/*
  • Existing files such as convex/auth.config.ts, auth middleware, provider wrappers
  • Environment variables that clearly point at a provider

Schema Setup

// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    tokenIdentifier: v.string(),
    name: v.string(),
    email: v.string(),
    pictureUrl: v.optional(v.string()),
    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"]),
});

Core Helper Functions

Get Current User

// 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();
}

export 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;
}

User Creation/Upsert

// 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");

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

    if (existingUser) {
      await ctx.db.patch(existingUser._id, { updatedAt: Date.now() });
      return existingUser._id;
    }

    return await ctx.db.insert("users", {
      tokenIdentifier: identity.tokenIdentifier,
      name: identity.name ?? "Anonymous",
      email: identity.email ?? "",
      pictureUrl: identity.pictureUrl,
      role: "user",
      createdAt: Date.now(),
    });
  },
});

Access Control Patterns

Resource Ownership

export 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");
    if (task.userId !== user._id) throw new Error("You can only delete your own tasks");
    await ctx.db.delete(args.taskId);
  },
});

Team-Based 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 };
}

Checklist

  • [ ] Chosen the correct auth provider before writing setup code
  • [ ] Users table with tokenIdentifier index
  • [ ] getCurrentUser helper function
  • [ ] storeUser mutation for first sign-in
  • [ ] Authentication check in all protected functions
  • [ ] Authorization check for resource access
  • [ ] Clear error messages ("Not authenticated", "Unauthorized")
  • [ ] Client auth provider configured

Source: https://github.com/get-convex/agent-skills

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.