[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CCC8S4RHB5DFP7BPCP02PV":3,"skill-same:01M6CCC8S4RHB5DFP7BPCP02PV":85},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":36,"commitSha":37,"skillDirectory":38,"forksCount":14,"repositoryHtmlUrl":39,"repositoryHomepageUrl":40,"ownerHtmlUrl":41,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":42,"install":43,"resources":56},"01M6CCC8S4RHB5DFP7BPCP02PV","auth-setup",null,"Set up Convex authentication with proper user management, identity mapping, and access control patterns. Use when implementing auth flows.","baisharg\u002FASTN","baisharg","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F204916775?v=4","01M6CC8XHZYVK2DR22FZ1BESCG","BAISH","01M6CC8XP400QZ9DXAWTC8EG3X",2,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fauth-setup\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30],"auth","setup","auth setup","convex","identity","authentication","SET","access","management","user","use","up","2026-11-10 10:12:30",false,"---\nname: auth-setup\ndescription: Set up Convex authentication with proper user management, identity mapping, and access control patterns. Use when implementing auth flows.\n---\n\n# Convex Authentication Setup\n\nImplement secure authentication in Convex with user management and access control.\n\n## When to Use\n\n- Setting up authentication for the first time\n- Implementing user management (users table, identity mapping)\n- Creating authentication helper functions\n- Setting up OAuth providers (WorkOS, Auth0, etc.)\n\n## Architecture Overview\n\nConvex authentication has two main parts:\n\n1. **Client Authentication**: Use a provider (WorkOS, Auth0, custom JWT)\n2. **Backend Identity**: Map auth provider identity to your users table\n\n## Schema Setup\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  users: defineTable({\n    \u002F\u002F From auth provider identity\n    tokenIdentifier: v.string(), \u002F\u002F Unique per auth provider\n\n    \u002F\u002F User profile data\n    name: v.string(),\n    email: v.string(),\n    pictureUrl: v.optional(v.string()),\n\n    \u002F\u002F Your app-specific fields\n    role: v.union(v.literal('user'), v.literal('admin')),\n\n    createdAt: v.number(),\n    updatedAt: v.optional(v.number()),\n  })\n    .index('by_token', ['tokenIdentifier'])\n    .index('by_email', ['email']),\n})\n```\n\n## Core Helper Functions\n\n### Get Current User\n\n```typescript\n\u002F\u002F convex\u002Flib\u002Fauth.ts\nimport { QueryCtx, MutationCtx } from '.\u002F_generated\u002Fserver'\nimport { Doc } from '.\u002F_generated\u002FdataModel'\n\nexport async function getCurrentUser(\n  ctx: QueryCtx | MutationCtx,\n): Promise\u003CDoc\u003C'users'>> {\n  const identity = await ctx.auth.getUserIdentity()\n  if (!identity) {\n    throw new Error('Not authenticated')\n  }\n\n  const user = await ctx.db\n    .query('users')\n    .withIndex('by_token', (q) =>\n      q.eq('tokenIdentifier', identity.tokenIdentifier),\n    )\n    .unique()\n\n  if (!user) {\n    throw new Error('User not found')\n  }\n\n  return user\n}\n\nexport async function getCurrentUserOrNull(\n  ctx: QueryCtx | MutationCtx,\n): Promise\u003CDoc\u003C'users'> | null> {\n  const identity = await ctx.auth.getUserIdentity()\n  if (!identity) {\n    return null\n  }\n\n  return await ctx.db\n    .query('users')\n    .withIndex('by_token', (q) =>\n      q.eq('tokenIdentifier', identity.tokenIdentifier),\n    )\n    .unique()\n}\n```\n\n### Require Admin\n\n```typescript\nexport async function requireAdmin(\n  ctx: QueryCtx | MutationCtx,\n): Promise\u003CDoc\u003C'users'>> {\n  const user = await getCurrentUser(ctx)\n\n  if (user.role !== 'admin') {\n    throw new Error('Admin access required')\n  }\n\n  return user\n}\n```\n\n## User Creation\u002FUpsert\n\n### On First Sign-In\n\n```typescript\n\u002F\u002F convex\u002Fusers.ts\nimport { mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport const storeUser = mutation({\n  args: {},\n  handler: async (ctx) => {\n    const identity = await ctx.auth.getUserIdentity()\n    if (!identity) {\n      throw new Error('Not authenticated')\n    }\n\n    \u002F\u002F Check if user exists\n    const existingUser = await ctx.db\n      .query('users')\n      .withIndex('by_token', (q) =>\n        q.eq('tokenIdentifier', identity.tokenIdentifier),\n      )\n      .unique()\n\n    if (existingUser) {\n      \u002F\u002F Update last seen or other fields\n      await ctx.db.patch(existingUser._id, {\n        updatedAt: Date.now(),\n      })\n      return existingUser._id\n    }\n\n    \u002F\u002F Create new user\n    const userId = await ctx.db.insert('users', {\n      tokenIdentifier: identity.tokenIdentifier,\n      name: identity.name ?? 'Anonymous',\n      email: identity.email ?? '',\n      pictureUrl: identity.pictureUrl,\n      role: 'user',\n      createdAt: Date.now(),\n    })\n\n    return userId\n  },\n})\n```\n\n## Access Control Patterns\n\n### Owner-Only Access\n\n```typescript\nimport { mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { getCurrentUser } from '.\u002Flib\u002Fauth'\n\nexport const updateProfile = mutation({\n  args: {\n    name: v.string(),\n  },\n  handler: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n\n    await ctx.db.patch(user._id, {\n      name: args.name,\n      updatedAt: Date.now(),\n    })\n  },\n})\n```\n\n### Resource Ownership\n\n```typescript\nexport const deleteTask = mutation({\n  args: { taskId: v.id('tasks') },\n  handler: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n\n    const task = await ctx.db.get(args.taskId)\n    if (!task) {\n      throw new Error('Task not found')\n    }\n\n    \u002F\u002F Check ownership\n    if (task.userId !== user._id) {\n      throw new Error('You can only delete your own tasks')\n    }\n\n    await ctx.db.delete(args.taskId)\n  },\n})\n```\n\n### Team-Based Access\n\n```typescript\n\u002F\u002F Schema includes membership table\nexport default defineSchema({\n  teams: defineTable({\n    name: v.string(),\n    ownerId: v.id('users'),\n  }),\n\n  teamMembers: defineTable({\n    teamId: v.id('teams'),\n    userId: v.id('users'),\n    role: v.union(v.literal('owner'), v.literal('member')),\n  })\n    .index('by_team', ['teamId'])\n    .index('by_user', ['userId'])\n    .index('by_team_and_user', ['teamId', 'userId']),\n})\n\n\u002F\u002F Helper to check team access\nasync function requireTeamAccess(\n  ctx: MutationCtx,\n  teamId: Id\u003C'teams'>,\n): Promise\u003C{ user: Doc\u003C'users'>; membership: Doc\u003C'teamMembers'> }> {\n  const user = await getCurrentUser(ctx)\n\n  const membership = await ctx.db\n    .query('teamMembers')\n    .withIndex('by_team_and_user', (q) =>\n      q.eq('teamId', teamId).eq('userId', user._id),\n    )\n    .unique()\n\n  if (!membership) {\n    throw new Error(\"You don't have access to this team\")\n  }\n\n  return { user, membership }\n}\n\n\u002F\u002F Use in functions\nexport const createProject = mutation({\n  args: {\n    teamId: v.id('teams'),\n    name: v.string(),\n  },\n  handler: async (ctx, args) => {\n    await requireTeamAccess(ctx, args.teamId)\n\n    return await ctx.db.insert('projects', {\n      teamId: args.teamId,\n      name: args.name,\n    })\n  },\n})\n```\n\n## Public vs Private Queries\n\n### Public Query (No Auth Required)\n\n```typescript\nexport const listPublicPosts = query({\n  args: {},\n  handler: async (ctx) => {\n    \u002F\u002F No auth check - anyone can read\n    return await ctx.db\n      .query('posts')\n      .withIndex('by_published', (q) => q.eq('published', true))\n      .collect()\n  },\n})\n```\n\n### Private Query (Auth Required)\n\n```typescript\nexport const getMyPosts = query({\n  args: {},\n  handler: async (ctx) => {\n    const user = await getCurrentUser(ctx)\n\n    return await ctx.db\n      .query('posts')\n      .withIndex('by_user', (q) => q.eq('userId', user._id))\n      .collect()\n  },\n})\n```\n\n### Hybrid Query (Optional Auth)\n\n```typescript\nexport const getPosts = query({\n  args: {},\n  handler: async (ctx) => {\n    const user = await getCurrentUserOrNull(ctx)\n\n    if (user) {\n      \u002F\u002F Show all posts including drafts for this user\n      return await ctx.db\n        .query('posts')\n        .withIndex('by_user', (q) => q.eq('userId', user._id))\n        .collect()\n    } else {\n      \u002F\u002F Show only public posts for anonymous users\n      return await ctx.db\n        .query('posts')\n        .withIndex('by_published', (q) => q.eq('published', true))\n        .collect()\n    }\n  },\n})\n```\n\n## Client Setup with WorkOS\n\nWorkOS AuthKit provides a complete authentication solution with minimal setup.\n\n### React\u002FVite Setup\n\n```bash\nnpm install @workos-inc\u002Fauthkit-react\n```\n\n```typescript\n\u002F\u002F src\u002Fmain.tsx\nimport { AuthKitProvider, useAuth } from \"@workos-inc\u002Fauthkit-react\";\nimport { ConvexReactClient } from \"convex\u002Freact\";\nimport { ConvexProvider } from \"convex\u002Freact\";\n\nconst convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);\n\n\u002F\u002F Configure Convex to use WorkOS auth\nconvex.setAuth(useAuth);\n\nfunction App() {\n  return (\n    \u003CAuthKitProvider clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}>\n      \u003CConvexProvider client={convex}>\n        \u003CYourApp \u002F>\n      \u003C\u002FConvexProvider>\n    \u003C\u002FAuthKitProvider>\n  );\n}\n```\n\n### Next.js Setup\n\n```bash\nnpm install @workos-inc\u002Fauthkit-nextjs\n```\n\n```typescript\n\u002F\u002F app\u002Flayout.tsx\nimport { AuthKitProvider } from \"@workos-inc\u002Fauthkit-nextjs\";\nimport { ConvexClientProvider } from \".\u002FConvexClientProvider\";\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Chtml>\n      \u003Cbody>\n        \u003CAuthKitProvider>\n          \u003CConvexClientProvider>\n            {children}\n          \u003C\u002FConvexClientProvider>\n        \u003C\u002FAuthKitProvider>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\n\nimport { ConvexReactClient } from \"convex\u002Freact\";\nimport { ConvexProvider } from \"convex\u002Freact\";\nimport { useAuth } from \"@workos-inc\u002Fauthkit-nextjs\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: React.ReactNode }) {\n  const { getToken } = useAuth();\n\n  convex.setAuth(async () => {\n    return await getToken();\n  });\n\n  return \u003CConvexProvider client={convex}>{children}\u003C\u002FConvexProvider>;\n}\n```\n\n### Environment Variables\n\n```bash\n# .env.local (React\u002FVite)\nVITE_CONVEX_URL=https:\u002F\u002Fyour-deployment.convex.cloud\nVITE_WORKOS_CLIENT_ID=your_workos_client_id\n\n# .env.local (Next.js)\nNEXT_PUBLIC_CONVEX_URL=https:\u002F\u002Fyour-deployment.convex.cloud\nNEXT_PUBLIC_WORKOS_CLIENT_ID=your_workos_client_id\nWORKOS_API_KEY=your_workos_api_key\nWORKOS_COOKIE_PASSWORD=generate_a_random_32_character_string\n```\n\n### Call storeUser on Sign-In\n\n```typescript\n\u002F\u002F In your app after user signs in\nimport { useMutation } from 'convex\u002Freact'\nimport { api } from '..\u002Fconvex\u002F_generated\u002Fapi'\nimport { useEffect } from 'react'\nimport { useAuth } from '@workos-inc\u002Fauthkit-react'\n\nfunction YourApp() {\n  const { user } = useAuth()\n  const storeUser = useMutation(api.users.storeUser)\n\n  useEffect(() => {\n    if (user) {\n      storeUser()\n    }\n  }, [user, storeUser])\n\n  \u002F\u002F ... rest of your app\n}\n```\n\n### Alternative Auth Providers\n\nIf you need to use a different provider, see the [Convex auth documentation](https:\u002F\u002Fdocs.convex.dev\u002Fauth) for:\n\n- Custom JWT\n- Auth0\n- Other OAuth providers\n\n## Checklist\n\n- [ ] Users table with `tokenIdentifier` index\n- [ ] `getCurrentUser` helper function\n- [ ] `storeUser` mutation for first sign-in\n- [ ] Authentication check in all protected functions\n- [ ] Authorization check for resource access\n- [ ] Clear error messages (\"Not authenticated\", \"Unauthorized\")\n- [ ] Client auth provider configured (WorkOS, Auth0, etc.)\n","# Convex Authentication Setup\n\nImplement secure authentication in Convex with user management and access control.\n\n## When to Use\n\n- Setting up authentication for the first time\n- Implementing user management (users table, identity mapping)\n- Creating authentication helper functions\n- Setting up OAuth providers (WorkOS, Auth0, etc.)\n\n## Architecture Overview\n\nConvex authentication has two main parts:\n\n1. **Client Authentication**: Use a provider (WorkOS, Auth0, custom JWT)\n2. **Backend Identity**: Map auth provider identity to your users table\n\n## Schema Setup\n\n```typescript\n\u002F\u002F convex\u002Fschema.ts\nimport { defineSchema, defineTable } from 'convex\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport default defineSchema({\n  users: defineTable({\n    \u002F\u002F From auth provider identity\n    tokenIdentifier: v.string(), \u002F\u002F Unique per auth provider\n\n    \u002F\u002F User profile data\n    name: v.string(),\n    email: v.string(),\n    pictureUrl: v.optional(v.string()),\n\n    \u002F\u002F Your app-specific fields\n    role: v.union(v.literal('user'), v.literal('admin')),\n\n    createdAt: v.number(),\n    updatedAt: v.optional(v.number()),\n  })\n    .index('by_token', ['tokenIdentifier'])\n    .index('by_email', ['email']),\n})\n```\n\n## Core Helper Functions\n\n### Get Current User\n\n```typescript\n\u002F\u002F convex\u002Flib\u002Fauth.ts\nimport { QueryCtx, MutationCtx } from '.\u002F_generated\u002Fserver'\nimport { Doc } from '.\u002F_generated\u002FdataModel'\n\nexport async function getCurrentUser(\n  ctx: QueryCtx | MutationCtx,\n): Promise\u003CDoc\u003C'users'>> {\n  const identity = await ctx.auth.getUserIdentity()\n  if (!identity) {\n    throw new Error('Not authenticated')\n  }\n\n  const user = await ctx.db\n    .query('users')\n    .withIndex('by_token', (q) =>\n      q.eq('tokenIdentifier', identity.tokenIdentifier),\n    )\n    .unique()\n\n  if (!user) {\n    throw new Error('User not found')\n  }\n\n  return user\n}\n\nexport async function getCurrentUserOrNull(\n  ctx: QueryCtx | MutationCtx,\n): Promise\u003CDoc\u003C'users'> | null> {\n  const identity = await ctx.auth.getUserIdentity()\n  if (!identity) {\n    return null\n  }\n\n  return await ctx.db\n    .query('users')\n    .withIndex('by_token', (q) =>\n      q.eq('tokenIdentifier', identity.tokenIdentifier),\n    )\n    .unique()\n}\n```\n\n### Require Admin\n\n```typescript\nexport async function requireAdmin(\n  ctx: QueryCtx | MutationCtx,\n): Promise\u003CDoc\u003C'users'>> {\n  const user = await getCurrentUser(ctx)\n\n  if (user.role !== 'admin') {\n    throw new Error('Admin access required')\n  }\n\n  return user\n}\n```\n\n## User Creation\u002FUpsert\n\n### On First Sign-In\n\n```typescript\n\u002F\u002F convex\u002Fusers.ts\nimport { mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\n\nexport const storeUser = mutation({\n  args: {},\n  handler: async (ctx) => {\n    const identity = await ctx.auth.getUserIdentity()\n    if (!identity) {\n      throw new Error('Not authenticated')\n    }\n\n    \u002F\u002F Check if user exists\n    const existingUser = await ctx.db\n      .query('users')\n      .withIndex('by_token', (q) =>\n        q.eq('tokenIdentifier', identity.tokenIdentifier),\n      )\n      .unique()\n\n    if (existingUser) {\n      \u002F\u002F Update last seen or other fields\n      await ctx.db.patch(existingUser._id, {\n        updatedAt: Date.now(),\n      })\n      return existingUser._id\n    }\n\n    \u002F\u002F Create new user\n    const userId = await ctx.db.insert('users', {\n      tokenIdentifier: identity.tokenIdentifier,\n      name: identity.name ?? 'Anonymous',\n      email: identity.email ?? '',\n      pictureUrl: identity.pictureUrl,\n      role: 'user',\n      createdAt: Date.now(),\n    })\n\n    return userId\n  },\n})\n```\n\n## Access Control Patterns\n\n### Owner-Only Access\n\n```typescript\nimport { mutation } from '.\u002F_generated\u002Fserver'\nimport { v } from 'convex\u002Fvalues'\nimport { getCurrentUser } from '.\u002Flib\u002Fauth'\n\nexport const updateProfile = mutation({\n  args: {\n    name: v.string(),\n  },\n  handler: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n\n    await ctx.db.patch(user._id, {\n      name: args.name,\n      updatedAt: Date.now(),\n    })\n  },\n})\n```\n\n### Resource Ownership\n\n```typescript\nexport const deleteTask = mutation({\n  args: { taskId: v.id('tasks') },\n  handler: async (ctx, args) => {\n    const user = await getCurrentUser(ctx)\n\n    const task = await ctx.db.get(args.taskId)\n    if (!task) {\n      throw new Error('Task not found')\n    }\n\n    \u002F\u002F Check ownership\n    if (task.userId !== user._id) {\n      throw new Error('You can only delete your own tasks')\n    }\n\n    await ctx.db.delete(args.taskId)\n  },\n})\n```\n\n### Team-Based Access\n\n```typescript\n\u002F\u002F Schema includes membership table\nexport default defineSchema({\n  teams: defineTable({\n    name: v.string(),\n    ownerId: v.id('users'),\n  }),\n\n  teamMembers: defineTable({\n    teamId: v.id('teams'),\n    userId: v.id('users'),\n    role: v.union(v.literal('owner'), v.literal('member')),\n  })\n    .index('by_team', ['teamId'])\n    .index('by_user', ['userId'])\n    .index('by_team_and_user', ['teamId', 'userId']),\n})\n\n\u002F\u002F Helper to check team access\nasync function requireTeamAccess(\n  ctx: MutationCtx,\n  teamId: Id\u003C'teams'>,\n): Promise\u003C{ user: Doc\u003C'users'>; membership: Doc\u003C'teamMembers'> }> {\n  const user = await getCurrentUser(ctx)\n\n  const membership = await ctx.db\n    .query('teamMembers')\n    .withIndex('by_team_and_user', (q) =>\n      q.eq('teamId', teamId).eq('userId', user._id),\n    )\n    .unique()\n\n  if (!membership) {\n    throw new Error(\"You don't have access to this team\")\n  }\n\n  return { user, membership }\n}\n\n\u002F\u002F Use in functions\nexport const createProject = mutation({\n  args: {\n    teamId: v.id('teams'),\n    name: v.string(),\n  },\n  handler: async (ctx, args) => {\n    await requireTeamAccess(ctx, args.teamId)\n\n    return await ctx.db.insert('projects', {\n      teamId: args.teamId,\n      name: args.name,\n    })\n  },\n})\n```\n\n## Public vs Private Queries\n\n### Public Query (No Auth Required)\n\n```typescript\nexport const listPublicPosts = query({\n  args: {},\n  handler: async (ctx) => {\n    \u002F\u002F No auth check - anyone can read\n    return await ctx.db\n      .query('posts')\n      .withIndex('by_published', (q) => q.eq('published', true))\n      .collect()\n  },\n})\n```\n\n### Private Query (Auth Required)\n\n```typescript\nexport const getMyPosts = query({\n  args: {},\n  handler: async (ctx) => {\n    const user = await getCurrentUser(ctx)\n\n    return await ctx.db\n      .query('posts')\n      .withIndex('by_user', (q) => q.eq('userId', user._id))\n      .collect()\n  },\n})\n```\n\n### Hybrid Query (Optional Auth)\n\n```typescript\nexport const getPosts = query({\n  args: {},\n  handler: async (ctx) => {\n    const user = await getCurrentUserOrNull(ctx)\n\n    if (user) {\n      \u002F\u002F Show all posts including drafts for this user\n      return await ctx.db\n        .query('posts')\n        .withIndex('by_user', (q) => q.eq('userId', user._id))\n        .collect()\n    } else {\n      \u002F\u002F Show only public posts for anonymous users\n      return await ctx.db\n        .query('posts')\n        .withIndex('by_published', (q) => q.eq('published', true))\n        .collect()\n    }\n  },\n})\n```\n\n## Client Setup with WorkOS\n\nWorkOS AuthKit provides a complete authentication solution with minimal setup.\n\n### React\u002FVite Setup\n\n```bash\nnpm install @workos-inc\u002Fauthkit-react\n```\n\n```typescript\n\u002F\u002F src\u002Fmain.tsx\nimport { AuthKitProvider, useAuth } from \"@workos-inc\u002Fauthkit-react\";\nimport { ConvexReactClient } from \"convex\u002Freact\";\nimport { ConvexProvider } from \"convex\u002Freact\";\n\nconst convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);\n\n\u002F\u002F Configure Convex to use WorkOS auth\nconvex.setAuth(useAuth);\n\nfunction App() {\n  return (\n    \u003CAuthKitProvider clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}>\n      \u003CConvexProvider client={convex}>\n        \u003CYourApp \u002F>\n      \u003C\u002FConvexProvider>\n    \u003C\u002FAuthKitProvider>\n  );\n}\n```\n\n### Next.js Setup\n\n```bash\nnpm install @workos-inc\u002Fauthkit-nextjs\n```\n\n```typescript\n\u002F\u002F app\u002Flayout.tsx\nimport { AuthKitProvider } from \"@workos-inc\u002Fauthkit-nextjs\";\nimport { ConvexClientProvider } from \".\u002FConvexClientProvider\";\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Chtml>\n      \u003Cbody>\n        \u003CAuthKitProvider>\n          \u003CConvexClientProvider>\n            {children}\n          \u003C\u002FConvexClientProvider>\n        \u003C\u002FAuthKitProvider>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\n\nimport { ConvexReactClient } from \"convex\u002Freact\";\nimport { ConvexProvider } from \"convex\u002Freact\";\nimport { useAuth } from \"@workos-inc\u002Fauthkit-nextjs\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: React.ReactNode }) {\n  const { getToken } = useAuth();\n\n  convex.setAuth(async () => {\n    return await getToken();\n  });\n\n  return \u003CConvexProvider client={convex}>{children}\u003C\u002FConvexProvider>;\n}\n```\n\n### Environment Variables\n\n```bash\n# .env.local (React\u002FVite)\nVITE_CONVEX_URL=https:\u002F\u002Fyour-deployment.convex.cloud\nVITE_WORKOS_CLIENT_ID=your_workos_client_id\n\n# .env.local (Next.js)\nNEXT_PUBLIC_CONVEX_URL=https:\u002F\u002Fyour-deployment.convex.cloud\nNEXT_PUBLIC_WORKOS_CLIENT_ID=your_workos_client_id\nWORKOS_API_KEY=your_workos_api_key\nWORKOS_COOKIE_PASSWORD=generate_a_random_32_character_string\n```\n\n### Call storeUser on Sign-In\n\n```typescript\n\u002F\u002F In your app after user signs in\nimport { useMutation } from 'convex\u002Freact'\nimport { api } from '..\u002Fconvex\u002F_generated\u002Fapi'\nimport { useEffect } from 'react'\nimport { useAuth } from '@workos-inc\u002Fauthkit-react'\n\nfunction YourApp() {\n  const { user } = useAuth()\n  const storeUser = useMutation(api.users.storeUser)\n\n  useEffect(() => {\n    if (user) {\n      storeUser()\n    }\n  }, [user, storeUser])\n\n  \u002F\u002F ... rest of your app\n}\n```\n\n### Alternative Auth Providers\n\nIf you need to use a different provider, see the [Convex auth documentation](https:\u002F\u002Fdocs.convex.dev\u002Fauth) for:\n\n- Custom JWT\n- Auth0\n- Other OAuth providers\n\n## Checklist\n\n- [ ] Users table with `tokenIdentifier` index\n- [ ] `getCurrentUser` helper function\n- [ ] `storeUser` mutation for first sign-in\n- [ ] Authentication check in all protected functions\n- [ ] Authorization check for resource access\n- [ ] Clear error messages (\"Not authenticated\", \"Unauthorized\")\n- [ ] Client auth provider configured (WorkOS, Auth0, etc.)\n",{"name":5,"description":7},"main","83212e6d909b6a0fe9e93fd5a3ff1723300f5b5b",".claude\u002Fskills\u002Fauth-setup","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN","https:\u002F\u002Fsafetytalent.org","https:\u002F\u002Fgithub.com\u002Fbaisharg","2026-11-04 07:01:33",{"source":8,"skillName":5,"commands":44,"prompt":54,"zipAvailable":55},[45,48,51],{"tool":46,"command":47},"npx","npx skills add baisharg\u002FASTN --skill auth-setup",{"tool":49,"command":50},"bunx","bunx skills add baisharg\u002FASTN --skill auth-setup",{"tool":52,"command":53},"pnpm","pnpm dlx skills add baisharg\u002FASTN --skill auth-setup","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fauth-setup\u002FSKILL.md",true,[57,63,67,71,75,79],{"path":58,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":61,"sourceHtmlUrl":62},"README.md","REFERENCE","参考文档",1799,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fcode-quality\u002FREADME.md",{"path":64,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":65,"sourceHtmlUrl":66},"references\u002Fbash.md",2936,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fcode-quality\u002Freferences\u002Fbash.md",{"path":68,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":69,"sourceHtmlUrl":70},"references\u002Frust.md",2471,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fcode-quality\u002Freferences\u002Frust.md",{"path":72,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":73,"sourceHtmlUrl":74},"references\u002Ftypescript.md",343,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fcode-quality\u002Freferences\u002Ftypescript.md",{"path":76,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":77,"sourceHtmlUrl":78},"references\u002Fui.md",633,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fcode-quality\u002Freferences\u002Fui.md",{"path":80,"resourceType":81,"resourceTypeDesc":82,"sizeBytes":83,"sourceHtmlUrl":84},"tests\u002Fshell-mktemp-root.test.sh","SCRIPT","脚本",10042,"https:\u002F\u002Fgithub.com\u002Fvanillagreencom\u002Fkendex\u002Fblob\u002Fmain\u002Fskills\u002Fcode-quality\u002Ftests\u002Fshell-mktemp-root.test.sh",[86,107,121,135,149,166],{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":97,"publishedAt":31},"01M6CCE5DVER8DQWJDY51HAJF7","schema-builder","Design and generate Convex database schemas with proper validation, indexes, and relationships. Use when creating schema.ts or modifying table definitions.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fschema-builder\u002FSKILL.md",[92],{"categoryNo":93,"categoryName":94,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",[98,99,100,101,102,29,103,104,105,22,106],"schema builder","schema","builder","indexes","validation","table","database","design","generate",{"skillNo":108,"name":109,"nameZh":6,"description":110,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":111,"categories":112,"keywords":113,"publishedAt":31},"01M6CCE36AA57CG36R4VHKFRXV","integration-tanstack-start","PostHog integration for TanStack Start full-stack applications","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fposthog-integration-tanstack-start\u002FSKILL.md",[],[114,115,116,117,118,119,120],"integration","start","tanstack","integration tanstack start","posthog","applications","stack",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":127,"publishedAt":31},"01M6CCE0FK6N8XAB093X7TTQ4F","migration-helper","Plan and execute Convex schema migrations safely, including adding fields, creating tables, and data transformations. Use when schema changes affect existing data.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fmigration-helper\u002FSKILL.md",[],[128,129,130,131,132,133,29,99,134,22],"migration","migration helper","schema migrations safely","data","plan","execute","migrations",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":141,"publishedAt":31},"01M6CCDY3644G8WXXP0BDNMZPR","gsd:sprint","Autonomous phase execution with Codex validation. Runs plan\u002Fexecute\u002Fverify cycles in a bash loop until done or halted.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-sprint\u002FSKILL.md",[],[137,142,143,102,115,144,145,146,147,132,148,133],"resume","codex","execution","verify","Bash","loop","autonomous",{"skillNo":150,"name":151,"nameZh":6,"description":152,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":155,"publishedAt":31},"01M6CCDVTZRTFWWGREMV6FF956","gsd:second-opinion","Get a second opinion from OpenAI Codex (GPT-5.2-codex) on code, architecture, or implementation decisions. Use when you want independent verification or an alternative perspective.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-second-opinion\u002FSKILL.md",[],[156,157,158,143,159,160,161,162,163,164,29,165],"gsd:second opinion","second opinion","question","architecture","code","verification","openai","gpt","decisions","implementation",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":172,"publishedAt":31},"01M6CCDSDPYYNEDERHP01N30PT","gsd:milestone-sprint","Run entire milestone autonomously with Codex validation. Auto-detects current milestone, executes all phases, and runs audit.","https:\u002F\u002Fgithub.com\u002Fbaisharg\u002FASTN\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgsd-milestone-sprint\u002FSKILL.md",[],[173,174,102,175,176,177,142,143],"gsd:milestone sprint","run","audit","interactive","auto"]