Authentication (app users)
Email-and-password app users, setup links, and sessions for static clients.
Authentication (app users)
Authentication lets static sites and client apps sign users in with email and password. Project admins enable it from Settings → Authentication, provision users, and manage suspend state.
Enablement
- Enable the Authentication capability on Settings → Authentication (checkbox + save), or use MCP
propose_authentication_configwithoperation: enable. - Leave Temporarily disable app-user sign-in unchecked when you are ready for login and setup endpoints to accept traffic. When sign-in is disabled, service keys and keyed content access still work.
- Set your auth page URL — the page on your static site where users set or reset their password. Provision and reset links use
?resetKey=on that URL. Use MCPpropose_authentication_configwithoperation: set_auth_page_urland{ "authPageUrl": "https://..." }(ornullto clear). - For browser sessions, call auth and session APIs on your site origin via the same-site path
/_arxdeck/api/*(Publish-hosted static and dynamic sites). That keeps thecontent_sessioncookie first-party. Platform-managed CORS still applies when you call the content service directly from the server or from credentialed cross-origin fetches (see Content management CORS checklist). - Provision users from the authentication settings page. To grant per-collection access rights to app users, enable both Authentication and Content management, then open the collection under Manage content.
Authentication does not require collections — you can run login and user management with no content.
Agents on ArxDeck MCP must call get_project_info for contentServiceUrl, projectRef, and capabilities.authentication before wiring login or session reads. Do not invent env-var configuration for static builds.
Self sign-up is optional per project. When enabled (Settings → Authentication or MCP set_signup_enabled), call GET /auth/config (public) to read { authEnabled, signupEnabled } before showing a registration form. POST /auth/register creates an active user and session in one step (blocked when sign-in is temporarily disabled or signup is off). Admins can still provision users manually.
Password setup and reset flow
When an admin provisions a user (or issues a reset), the dashboard generates a one-time setup token. Share it with the user as a link on your auth page:
- Configure auth page URL in Settings → Authentication (for example
https://www.example.com/auth). - Append the token as query parameter
resetKey(same value assetupTokenin the API):- If the URL has no query string:
{authPageUrl}?resetKey={setupToken} - If the URL already has query parameters:
{authPageUrl}&resetKey={setupToken}
- If the URL has no query string:
- On your auth page, read
resetKeyfrom the query string and callPOST /v1/auth/setupwith body{ "setupToken": "<resetKey>", "email", "password" }. - Optional
displayNamemay be set on setup.
If auth page URL is not configured, the dashboard shows the raw setup token for manual copy.
Successful setup clears the setup token, activates the user, and returns a 7-day session (content_session cookie).
API reference
App-user auth endpoints live on the content service. Browser clients on Publish-hosted sites should use the same-site base below so Chrome and other browsers store the session cookie. Server-side code may call the content service URL directly (unchanged).
Browser base URL (Publish)
https://{your-published-hostname}/_arxdeck/apiExample: POST https://my-app.app.arxdeck.ai/_arxdeck/api/auth/login with credentials: 'include'.
Direct content service base (server-side)
{content-service-base}/v1Replace {content-service-base} with the HTTPS origin from Settings → Content management. Paths below are shown as /auth/...; prefix with /_arxdeck/api on the site origin or /v1 on the content service.
Headers
| Header | Required | Description |
|---|---|---|
X-Project-Ref | Yes | Project ref from Settings → Content management. |
Origin | Browser requests | Must match a platform-managed publish hostname or a manual allowed origin for CORS. |
Content-Type | JSON bodies | application/json on POST / PUT. |
Cookie | Session routes | content_session set by login/setup responses. |
Sessions
- Sessions last 7 days from creation (login or setup).
- The service sets an HttpOnly
content_sessioncookie (SameSite=Lax; Secure). Browsers do not treat it as a third-party cookie when you use the same-site/_arxdeck/apipath on your publish hostname. - Cross-origin browser calls directly to
{content-service-base}may not persist the cookie even when CORS allows credentials — prefer the same-site path for static and SPA clients. POST /auth/logout(or/v1/auth/logouton the content service) clears the cookie.- Suspended users receive
403on session-authenticated routes.
Common errors
| Status | Meaning |
|---|---|
400 | Invalid JSON or validation failed. |
401 | Missing/invalid session or invalid login credentials. |
403 | Auth disabled for project, invalid/expired setup token, setup not available, suspended user, or origin not allowed. |
404 | Unknown project ref or user not found (GET /auth/me). |
413 | User data document exceeds 64 KiB serialized JSON. |
429 | Rate limit exceeded (see Content management rate limits). |
Endpoints
POST /auth/setup
Set password for a provisioned user (first-time or after admin reset).
| Field | Type | Required | Description |
|---|---|---|---|
setupToken | string | Yes | Token from the setup link (resetKey query param). |
email | string | Yes | User email (must match provisioned user). |
password | string | Yes | New password (8–200 characters). |
displayName | string | No | Optional display name (max 120). |
Response — 201 Created
{
"user": {
"id": "clx9usr00000000000000001",
"email": "editor@example.com",
"displayName": "Editor"
}
}Sets content_session cookie.
curl -X POST "{content-service-base}/v1/auth/setup" \
-H "Content-Type: application/json" \
-H "X-Project-Ref: YOUR_PROJECT_REF" \
-H "Origin: https://www.example.com" \
-d '{
"setupToken": "TOKEN_FROM_RESET_KEY",
"email": "editor@example.com",
"password": "chosen-password"
}'GET /auth/config
Public check for whether sign-in and self sign-up are enabled. No session required.
Response — 200 OK — { "authEnabled": true, "signupEnabled": false }
curl "{content-service-base}/v1/auth/config" \
-H "X-Project-Ref: YOUR_PROJECT_REF"POST /auth/register
Available when signupEnabled is true and app-user sign-in is not temporarily disabled.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User email. |
password | string | Yes | Password (8–200 characters). |
displayName | string | No | Optional display name. |
Response — 201 Created — { "user": { "id", "email", "displayName" } }; sets content_session cookie. Duplicate email returns 409.
POST /auth/login
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User email. |
password | string | Yes | User password. |
Response — 200 OK — same user object as setup; sets content_session cookie.
curl -X POST "{content-service-base}/v1/auth/login" \
-H "Content-Type: application/json" \
-H "X-Project-Ref: YOUR_PROJECT_REF" \
-d '{
"email": "editor@example.com",
"password": "user-password"
}'POST /auth/change-password
Requires active session.
| Field | Type | Required | Description |
|---|---|---|---|
currentPassword | string | Yes | Existing password. |
newPassword | string | Yes | New password (8–200 characters). |
Response — 200 OK — { "ok": true }.
POST /auth/logout
Clears session cookie. Response — 200 OK — { "ok": true }.
GET /auth/me
Requires active session.
Response — 200 OK
{
"user": {
"id": "clx9usr00000000000000001",
"email": "editor@example.com",
"displayName": "Editor",
"status": "active"
},
"projectRef": "YOUR_PROJECT_REF",
"access": {
"pages": "read",
"blog": "write"
}
}access maps collection slug to the signed-in user's effective level: explicit per-user or group grants (highest wins) plus read for public-readable collections (public visibility, not collection-suspended, project public access not suspended). Collections the user cannot read are omitted. Service keys should use the per-row access field on GET /collections instead of this endpoint.
GET /auth/user-data
Requires active session and authEnabled on the project.
Response — 200 OK — { "data": { ... } } (empty object if never written).
Only the signed-in user can read their document. Service keys cannot access user data in v1.
PUT /auth/user-data
Replace the entire user data document (not a merge).
| Field | Type | Required | Description |
|---|---|---|---|
data | object | Yes | Free-form JSON object, max 64 KiB serialized. |
Response — 200 OK — { "data": { ... } } with stored document.
curl -X PUT "{content-service-base}/v1/auth/user-data" \
-H "Content-Type: application/json" \
-H "X-Project-Ref: YOUR_PROJECT_REF" \
-H "Cookie: content_session=..." \
-d '{ "data": { "onboardingComplete": true, "theme": "dark" } }'Static client patterns
| Approach | When to use |
|---|---|
| First user / reset | Open <auth-page-url>?resetKey=… or call POST /_arxdeck/api/auth/setup on your site origin with setupToken mapped from resetKey. |
| Session cookie | After POST /_arxdeck/api/auth/login on your publish hostname, use fetch(..., { credentials: 'include' }) for /_arxdeck/api/auth/me, user-data, and private collection reads. |
| Service key | Server-side content API only. Never ship cbk_ keys in static JS bundles. |
Custom hosting (not on Publish)
Host your own same-origin reverse proxy so browser traffic hits your domain first, then forwards to {content-service-base}/v1/.... Example (Next.js rewrites in next.config.js):
async rewrites() {
return [
{
source: "/_arxdeck/api/:path*",
destination: `${process.env.CONTENT_SERVICE_URL}/v1/:path*`,
},
];
}Use server-side fetch from API routes when you prefer service keys instead of forwarding cookies. Dynamic publish backends can implement the same proxy in-app or call the content service with service keys without app-user sessions.
Per-user private data
Each app user has one JSON object (profile, preferences, onboarding state, settings, and so on). The document is free-form JSON with a 64 KiB serialized size cap. Only that user can read or write it over their session.
Isolation: a session can only access the caller's own document. Suspended users and logged-out callers receive errors; another user's id is not accepted on session routes.
What agents can change (MCP)
get_authentication_config— capability,authEnabled, andauthPageUrlpropose_authentication_config— enable/disable capability, toggle app-user sign-in, set auth page URL (set_auth_page_url)
Related
- Content management — collections, CORS, service keys
- Integrations overview
