Authentication
Users, local/OAuth login, tokens, teams, 2FA, and sudo for sensitive operations.
Apps need sign-up, login, token lifecycle, and optional multi-tenant membership without building auth from scratch. The authentication module handles user accounts, JWT access/refresh tokens, OAuth providers, teams, magic links, and 2FA. Application runtime code uses the Client API with user bearer tokens; provisioning uses the Admin API or MCP.
For AI agents
Machine-to-machine and operator automation use Admin API tokens (cdt_ prefix) — not Client API service accounts. See Client vs Admin API.
Use cases
User sign-up and login
Email/password or OAuth with JWT access and refresh tokens
Multi-tenant B2B
Teams with hierarchy, invites, and ReBAC-backed membership
Secure web apps
Server-side token vault — tokens never in browser storage
Sensitive account changes
Sudo mode after fresh login gates password, email, 2FA, and team deletion
CI and provisioning
Admin API `cdt_` tokens or MCP for operator workflows — not end-user tokens
Capabilities
- Local register/login
- OAuth (Google, GitHub, Apple, Microsoft, Facebook, Slack, GitLab, …)
- Access + refresh tokens (JWT)
- Sudo mode for destructive/sensitive routes
- Magic link login
- 2FA (authenticator + SMS)
- 2FA backup codes (config-gated)
- Teams, invites, and sub-teams
- Email verification & password reset
- User profile extension fields
- Anonymous users (optional)
- Account linking (same email across providers)
Example: Register, login, and invite a teammate
Walkthrough
- Enable the authentication module and local strategy via MCP `patch_config_authentication`
- Set `teams.enabled: true` and configure invites if needed
- Register a user with POST /authentication/local/new (pass `invitationToken` when invites are required)
- Log in with POST /authentication/local — store `{ accessToken, refreshToken }` server-side
- Create a team via POST /authentication/teams or MCP `post_authentication_teams`
- Invite a teammate with POST /authentication/teams/:teamId/invite
curl -X POST http://localhost:3000/authentication/local/new \
-H "Content-Type: application/json" \
-d '{"email":"dev@example.com","password":"SecurePass123!"}'curl -X POST http://localhost:3000/authentication/local \
-H "Content-Type: application/json" \
-d '{"email":"dev@example.com","password":"SecurePass123!"}'Register returns { user } only — the user must log in separately for tokens.
How it works
Token lifecycle
Access tokens are JWTs (default 1h via accessTokens.expiryPeriod). The payload includes:
| Claim | Meaning |
|---|---|
id | User ID |
authorized | true when login is complete (including 2FA if enabled) |
sudo | true on tokens issued from a fresh login — required for sensitive routes |
When refreshTokens.enabled is true, POST /authentication/renew rotates the refresh token and issues a new pair. Renewed tokens set sudo: false — the user must re-authenticate for destructive operations. Blocked users (active: false) cannot renew or log in.
clients.multipleUserSessions and clients.multipleClientLogins control whether new logins invalidate existing tokens per client or globally.
Sudo mode
Several routes require jwtPayload.sudo === true on the access token. Sudo is granted only when:
- The user completed login (and 2FA, if enabled), and
- Tokens were issued from a login flow — not from
/authentication/renew
If sudo is missing, the API returns PERMISSION_DENIED with "Re-login required to enter sudo mode".
| Route | Action |
|---|---|
DELETE /authentication/user | Delete own account |
POST /authentication/local/change-password | Change password |
POST /authentication/local/change-email | Change email |
PUT /authentication/twoFa/enable | Enable 2FA |
PUT /authentication/twoFa/disable | Disable 2FA |
GET /authentication/twoFa/generate | Generate backup codes |
DELETE /authentication/teams/:teamId | Delete team |
Pattern: prompt the user to log in again before sensitive settings changes; use the new access token for the sudo-gated call.
Application token storage
Web / Next.js: Store { accessToken, refreshToken } in a server-side vault (Redis). The browser holds only an opaque session cookie. On 401, renew once and retry. See the Next.js guide.
Mobile: Platform secure storage (Keychain, SecureStore) when no server vault exists.
Never persist tokens in localStorage or sessionStorage.
Two-factor authentication (2FA)
Enable globally with twoFa.enabled. Per-method toggles:
| Config key | Method value | Notes |
|---|---|---|
twoFa.methods.authenticator | "authenticator" | TOTP via authenticator app |
twoFa.methods.sms | "sms" | SMS via the communications module — use method name sms, not phone |
Login with 2FA: POST /authentication/local returns a challenge (SMS sent or OTP required) instead of full tokens. Complete with POST /authentication/twoFa/verify and the code.
Enable flow: PUT /authentication/twoFa/enable with { "method": "sms", "phoneNumber": "+1..." } or { "method": "authenticator" } (requires sudo). Confirm via POST /authentication/twoFa/enable/verify.
Backup codes: When twoFa.backUpCodes.enabled is true, these routes are registered:
| Route | Purpose |
|---|---|
GET /authentication/twoFa/generate | Generate 10 one-time codes (requires sudo) |
POST /authentication/twoFa/recover | Recover access with an 8-digit backup code during login |
When twoFa.backUpCodes.enabled is false, backup-code routes are not exposed.
Teams
Enable with teams.enabled. Teams register as a built-in ReBAC resource (Team) with relations member, owner, and team-scoped permissions. See Authorization and ReBAC team scoping.
| Concept | Behavior |
|---|---|
| Flat B2B | Each root Team (parentTeam: null) is a tenant boundary |
| Sub-teams | parentTeam on create sets Team:parent#owner@Team:child for permission inheritance |
| Invites | teams.invites.enabled — email invites with invitationToken on register/OAuth |
| Default team | teams.enableDefaultTeam auto-creates a personal team per user |
Key Client API routes (all require authentication):
| Operation | Path |
|---|---|
| List my teams | GET /authentication/teams |
| Create team | POST /authentication/teams |
| Get / update / delete team | GET|PATCH|DELETE /authentication/teams/:teamId |
| Members | GET|PATCH|DELETE /authentication/teams/:teamId/members |
| Sub-teams | GET /authentication/teams/:teamId/teams |
| Invite user | POST /authentication/teams/:teamId/invite |
| Accept invite | GET /authentication/teams/invite/accept?invitationToken=… |
| Pending invites | GET /authentication/teams/invites |
Team delete requires sudo. Admin CRUD uses MCP tools like get_authentication_teams and post_authentication_teams.
OAuth
When a provider is enabled with clientId + clientSecret, routes are registered per provider:
| Step | Path |
|---|---|
| Start redirect flow | GET /authentication/init/{provider} |
| Native/mobile init | GET /authentication/initNative/{provider} |
| IdP callback | GET /authentication/hook/{provider} |
Supported providers include Google, GitHub, Apple, Microsoft, Facebook, Slack, GitLab, Twitter, Twitch, Figma, Reddit, Bitbucket, LinkedIn, and Metamask (when configured).
Pass invitationToken and redirectUri on init to join a team during OAuth sign-up. Tokens return in JSON or httpOnly cookies depending on accessTokens.setCookie / refreshTokens.setCookie. redirectUris.whitelistedUris restricts post-auth redirects.
Machine auth (service accounts removed in v0.17)
v0.17 removes Client API service accounts (POST /authentication/service) as a breaking change. The Service collection is dropped on authentication module startup migration. For automation, CI, and MCP clients, use Admin API tokens instead:
- Create
cdt_API tokens via Admin API (POST /api-tokens) or MCPpost_apitokens - Authenticate MCP with
Authorization: Bearer <cdt_token> - Never embed admin tokens in application runtime code or browser config
Until you upgrade from v0.16, legacy deployments may still expose service-account login behind service.enabled — plan migration before promoting v0.17. See Migration v0.16 → v0.17.
Configure
Patch module config via MCP patch_config_authentication:
| Key | Default | Meaning |
|---|---|---|
accessTokens.expiryPeriod | 3600000 (1h) | Access JWT lifetime (ms) |
accessTokens.setCookie | false | Return tokens as httpOnly cookies |
refreshTokens.enabled | true | Enable /authentication/renew |
refreshTokens.expiryPeriod | 604800000 (7d) | Refresh token lifetime |
local.enabled | true | Email/password strategy |
local.verification.required | false | Block login until email verified |
local.verification.method | link | link or code verification |
teams.enabled | false | Team routes and team-scoped ReBAC |
teams.invites.enabled | false | Email team invitations |
teams.enableDefaultTeam | false | Auto-create personal team on register |
twoFa.enabled | false | Global 2FA toggle |
twoFa.methods.sms | false | SMS 2FA (requires communications module) |
twoFa.methods.authenticator | true | TOTP authenticator 2FA |
twoFa.backUpCodes.enabled | true | Expose backup-code generate/recover routes |
magic_link.enabled | false | Passwordless email login |
anonymousUsers.enabled | false | Anonymous user registration |
OAuth providers need clientId + clientSecret per provider under config.{provider}.enabled.
Local and OAuth routes require router client id/secret in request context (Conduit router client credentials). Email verification, magic links, and invite emails require the communications module.
Client API
| Operation | Path |
|---|---|
| Register | POST /authentication/local/new |
| Login | POST /authentication/local |
| Renew | POST /authentication/renew |
| Logout | POST /authentication/logout |
| Current user | GET /authentication/user |
| Update user | PATCH /authentication/user |
| Delete user | DELETE /authentication/user (sudo) |
| Change password | POST /authentication/local/change-password (sudo) |
| Forgot / reset password | POST /authentication/forgot-password, POST /authentication/reset-password |
| Magic link | POST /authentication/magic-link |
| OAuth init | GET /authentication/init/{provider} |
| 2FA verify | POST /authentication/twoFa/verify |
| 2FA enable / disable | PUT /authentication/twoFa/enable, PUT /authentication/twoFa/disable (sudo) |
| 2FA backup codes | GET /authentication/twoFa/generate, POST /authentication/twoFa/recover (when twoFa.backUpCodes.enabled) |
| Teams | GET /authentication/teams, POST /authentication/teams, GET /authentication/teams/:teamId, … |
MCP
Enable with ?modules=authentication in your MCP server URL.
| Tool | Purpose |
|---|---|
get_authentication_users | List users |
post_authentication_users | Create user (admin) |
get_authentication_teams | List teams |
post_authentication_teams | Create team |
patch_config_authentication | Token, OAuth, team, and 2FA settings |
For operator automation, prefer cdt_ API tokens over legacy service-account config. See MCP setup.