원클릭으로
convex-setup-auth
// Set up Convex authentication with proper user management, identity mapping, and access control patterns. Use when implementing auth flows, setting up OAuth providers, or adding role-based access control.
// Set up Convex authentication with proper user management, identity mapping, and access control patterns. Use when implementing auth flows, setting up OAuth providers, or adding role-based access control.
Integrate and maintain Robelest Convex Auth in apps by always checking upstream before implementation. Use when adding auth setup, updating auth wiring, migrating between upstream patterns, or troubleshooting @robelest/convex-auth behavior across projects.
Initialize a new Convex project from scratch or add Convex to an existing app. Use when starting a new project with Convex, scaffolding a Convex app, or integrating Convex into an existing frontend.
Full-stack Convex development guidelines covering React, Vite, TypeScript, mutations, auth, design system, and documentation practices. Use when building features, writing Convex functions, or making code changes in this project.
Create Convex queries, mutations, and actions with proper validation, authentication, and error handling. Use when implementing new API endpoints.
Critical Git safety rules to prevent destructive operations. Use whenever performing git operations, reverting changes, or managing commits. Prevents accidental loss of work.
Reflection-first problem solving methodology for Convex development. Use before implementing any solution to ensure proper root cause analysis, 98% code confidence, and minimal change scope.
| name | convex-setup-auth |
| description | Set up Convex authentication with proper user management, identity mapping, and access control patterns. Use when implementing auth flows, setting up OAuth providers, or adding role-based access control. |
Implement secure authentication in Convex with user management and access control.
Do not assume a provider. Before writing setup code:
Common options:
Look for signals in the repo before asking:
@clerk/*, @workos-inc/*, @auth0/*convex/auth.config.ts, auth middleware, provider wrappers// 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"]),
});
// 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;
}
// 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(),
});
},
});
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);
},
});
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 };
}
tokenIdentifier indexgetCurrentUser helper functionstoreUser mutation for first sign-in