- name
- robel-auth
- description
- 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.
# Robel auth skill
Use this skill when a user asks to implement, update, or debug auth based on `robelest/convex-auth`.
This skill is designed to be copied into other repos.
## Non negotiable upstream check before any auth change
Run this every time before proposing code or commands.
Preferred command:
```bash
bash .agents/skills/robel-auth/scripts/check-upstream.sh
```
Manual checklist if script is unavailable:
1. Read official docs: `https://auth.estifanos.com/getting-started/installation/`
2. Read latest `main` README: `https://raw.githubusercontent.com/robelest/convex-auth/main/README.md`
3. Read latest `release` README: `https://raw.githubusercontent.com/robelest/convex-auth/release/README.md`
4. Check branch level differences: `https://github.com/robelest/convex-auth/compare/release...main`
5. Read current self hosting docs if portal or static hosting is involved:
- `https://raw.githubusercontent.com/get-convex/self-hosting/main/INTEGRATION.md`
- `https://github.com/get-convex/self-hosting`
If `main` and `release` conflict, prefer the branch requested by the user. If unspecified, use `release` for stability and explain that choice.
## Important assumptions for this skill
- Treat the official docs site (`auth.estifanos.com`) and GitHub as sources of truth every time.
- Do not assume npm package availability.
- Validate package availability at execution time.
- If npm is unavailable, use a GitHub source install pinned to a branch or commit.
- Keep all Convex code type safe and validator complete.
## Published package reality check (critical)
The docs site (`auth.estifanos.com`) and `main` branch often describe APIs ahead of the latest preview release on npm. Before writing imports, always inspect what the installed version actually exports:
```bash
ls node_modules/@robelest/convex-auth/dist/providers/
cat node_modules/@robelest/convex-auth/dist/providers/index.js
cat node_modules/@robelest/convex-auth/dist/component/index.d.ts
```
As of `0.0.4-preview.25` the installed package ships:
- PascalCase class/factory exports: `Password`, `OAuth`, `Passkey`, `Credentials`, `Anonymous`, `Device`, `Email`, `Phone`, `SSO`, `Totp` (not lowercase `password`, `oauth`, etc. that the docs show).
- `Password`, `Credentials`, etc. are classes. Use `new Password()`, not `Password()`.
- `OAuth` is a factory function: `OAuth(provider, config)`.
- No first-party provider files exist yet (no `github.js`, `google.js`, etc.). For GitHub, Google, Apple, Microsoft, you still wrap an `arctic` provider in `OAuth()` and supply a `profile` callback.
- `createAuth` is available from `@robelest/convex-auth/component` and is the correct entry point.
- The client factory now **requires** `api` in SPA mode: `client({ convex, api: api.auth })`. Omitting `api` throws `"The \`api\` option is required when \`proxyPath\` is not set. Pass { api: api.auth }."` at first `signIn`/`signOut`/`verifyCode` call. Only `proxyPath` mode can skip it.
If the docs show a lowercase factory like `github({ ... })` but the installed package only exports PascalCase classes and `OAuth`, follow the installed package and note the drift to the user. Do not blindly copy doc examples.
## Clarifying questions to ask first
Ask these before editing:
1. Which branch is source of truth for this task, `release` or `main`.
2. Is this a new integration or an update to an existing auth setup.
3. Which framework is used: Vite, Next.js, SvelteKit, TanStack Start, Expo web, or other.
4. Is self hosted portal/static delivery needed now.
5. Are they okay pinning dependency to a specific Git commit for reproducibility.
## Install and dependency strategy
Never assume one install path.
1. Try package registry lookup:
- `npm view @robelest/convex-auth version`
2. If package is unavailable or blocked, install from GitHub:
- `npm install github:robelest/convex-auth#release`
3. For deterministic builds, pin a commit SHA:
- `npm install github:robelest/convex-auth#<commit-sha>`
If the project uses pnpm or bun, translate the same GitHub dependency pinning pattern.
## Quick setup (CLI wizard)
The recommended setup flow:
1. Install `@robelest/convex-auth`
2. Start a Convex deployment with `convex dev`
3. Run the auth setup wizard
The wizard handles key generation, `convex.config.ts`, `auth.ts`, and `http.ts` automatically.
## Core wiring (3 files)
### 1. Register the component
```typescript
// convex/convex.config.ts
import { defineApp } from "convex/server";
import auth from "@robelest/convex-auth/convex.config";
const app = defineApp();
app.use(auth);
export default app;
```
### 2. Configure auth
```typescript
// convex/auth.ts
import { createAuth } from "@robelest/convex-auth/component";
import { components } from "./_generated/api";
import { github } from "@robelest/convex-auth/providers/github";
const auth = createAuth(components.auth, {
providers: [
github({
clientId: process.env.AUTH_GITHUB_ID!,
clientSecret: process.env.AUTH_GITHUB_SECRET!,
}),
],
});
export { auth };
export const { signIn, signOut, store } = auth;
```
### 3. Wire up HTTP routes
```typescript
// convex/http.ts
import { httpRouter } from "convex/server";
import { auth } from "./auth";
const http = httpRouter();
auth.http.add(http);
export default http;
```
`auth.http.add` registers OAuth callbacks and JWKS endpoints in one call.
## API layers
Client auth flow: `signIn`, `signOut`, and `store` are the only required client-callable auth functions. Frontends use them through `client({ convex, api: api.auth })`.
Server helpers: `auth.user.*`, `auth.session.*`, `auth.account.*`, `auth.group.*`, `auth.member.*`, `auth.invite.*`, `auth.key.*`, `auth.http.*`, `auth.group.sso.*`, and `auth.group.sso.scim.*` are server-side helpers for Convex code. They are not automatically public RPC.
Optional group SSO RPC: If your app wants client-callable group SSO admin APIs, expose app-owned wrappers such as `convex/auth/group.ts`.
## Available providers
All providers import from `@robelest/convex-auth/providers`:
```typescript
import {
anonymous,
apple,
custom,
email,
github,
google,
microsoft,
passkey,
password,
phone,
sso,
totp,
} from "@robelest/convex-auth/providers";
```
### OAuth providers
| Provider | Factory | Required env vars |
|----------|---------|-------------------|
| GitHub | `github({ clientId, clientSecret })` | `AUTH_GITHUB_ID`, `AUTH_GITHUB_SECRET` |
| Google | `google({ clientId, clientSecret })` | `AUTH_GOOGLE_ID`, `AUTH_GOOGLE_SECRET` |
| Apple | `apple({ clientId, teamId, keyId, privateKey })` | `AUTH_APPLE_ID`, `AUTH_APPLE_TEAM_ID`, `AUTH_APPLE_KEY_ID`, `AUTH_APPLE_PRIVATE_KEY` |
| Microsoft | `microsoft({ tenant, clientId, clientSecret? })` | `AUTH_MICROSOFT_TENANT_ID`, `AUTH_MICROSOFT_ID` |
All OAuth wrappers derive callback URL from `CONVEX_SITE_URL` automatically.
### Custom OAuth
Use `custom()` for providers without a first-party wrapper:
```typescript
custom({
id: "discord",
clientId: process.env.AUTH_DISCORD_ID!,
clientSecret: process.env.AUTH_DISCORD_SECRET!,
scopes: ["identify", "email"],
authorization: { url: "https://discord.com/oauth2/authorize", pkce: "optional" },
token: { url: "https://discord.com/api/oauth2/token", authMethod: "body" },
profile: async ({ accessToken }) => {
const res = await fetch("https://discord.com/api/users/@me", {
headers: { Authorization: `Bearer ${accessToken}` },
});
const user = await res.json();
return { id: String(user.id), email: user.email, name: user.username };
},
})
```
### Non-OAuth providers
| Provider | Factory | Notes |
|----------|---------|-------|
| Password | `password()` | Built-in password auth |
| Magic links | `email({ from, send })` | Requires email transport (e.g. Resend) |
| Passkeys | `passkey()` | WebAuthn based |
| TOTP | `totp({ issuer })` | Authenticator app codes |
| Anonymous | `anonymous()` | Guest sessions |
| Phone/SMS | `phone({ send })` | Requires SMS transport (e.g. Twilio) |
| Group SSO | `sso()` | Enables OIDC, SAML 2.0, SCIM 2.0 |
## Configuration options
```typescript
const auth = createAuth(components.auth, {
providers: [/* ... */],
session: {
totalDurationMs: 30 * 24 * 60 * 60 * 1000, // 30 days
inactiveDurationMs: 7 * 24 * 60 * 60 * 1000, // 7 days
},
jwt: {
durationMs: 60 * 1000, // 1 minute
},
signIn: {
max_failed_attempts_per_hour: 10,
},
callbacks: {
afterUserCreatedOrUpdated: async (ctx, { userId, existingUser }) => { /* ... */ },
},
authorization: {
roles, // from defineRoles()
},
});
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `providers` | `AuthProviderConfig[]` | required | Auth methods to enable |
| `session.totalDurationMs` | `number` | 30 days | Maximum session lifetime |
| `session.inactiveDurationMs` | `number` | varies | Inactive session timeout |
| `jwt.durationMs` | `number` | 60s | JWT token lifetime |
| `signIn.max_failed_attempts_per_hour` | `number` | 10 | Rate limit for failed sign-in attempts |
| `callbacks.afterUserCreatedOrUpdated` | `function` | none | Post sign-in hook |
| `authorization.roles` | `Record` | `{}` | App-defined role definitions and grants |
## Multi-access patterns
Every auth path resolves to the same `userId`. Three access patterns:
| Pattern | Context | How userId is available |
|---------|---------|------------------------|
| App code (query/mutation/action) | `auth.ctx()` | `ctx.auth.userId` and `ctx.auth.user` |
| Raw HTTP (session or API key) | `auth.http.context(ctx, request)` | `authContext.userId` |
| API key HTTP | `auth.http.action(...)` | `ctx.key.userId` |
### Auth-aware custom functions
```typescript
// convex/functions.ts
import { customMutation, customQuery } from "convex-helpers/server/customFunctions";
import { mutation, query } from "./_generated/server";
import { auth } from "./auth";
export const authQuery = customQuery(query, auth.ctx());
export const authMutation = customMutation(mutation, auth.ctx());
```
Use `auth.ctx({ optional: true })` when the same handler should work for both guests and signed-in users.
### Raw HTTP mixed auth
```typescript
http.route({
path: "/api/data",
method: "GET",
handler: httpAction(async (ctx, request) => {
const authContext = await auth.http.context(ctx, request, { optional: true });
if (authContext.userId === null) {
return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
}
const data = await ctx.runQuery(internal.data.forUser, { userId: authContext.userId });
return Response.json(data);
}),
});
```
## Authorization patterns
### Define roles with grants
```typescript
import { defineRoles } from "@robelest/convex-auth/authorization";
export const roles = defineRoles({
orgAdmin: {
label: "Organization Admin",
Ver en GitHub