Contents
- Domma CMS User Manual
- 1. Using the CMS
- 2. Tutorials
- 3. Components
- 4. API Reference
- 5. Tools
Authentication
Updated by Darryl Waterhouse on 29 September 2026 · 5 min read
The admin signs in with a short-lived access token (sent as Authorization: Bearer <token>) and a longer refresh token that gets a new access token. The lifetimes are set in config/auth.json (default 15 minutes and 7 days). Each sign-in is a session kept on disk, so signing out holds across a restart, and a new password ends the account's other sessions. External programs should use an API token instead - see External API & tokens.
GET /api/auth/setup-status
No authentication required.
Whether the site still needs its first account, plus what the sign-in screen needs to know.
// Response 200
{ "needsSetup": false, "siteTitle": "My Site", "resetByEmail": true, "resetExpiresIn": "1 hour" }
POST /api/auth/setup
No authentication required. Only succeeds while the site has no users.
Create the first account, as the level-0 role (super-admin), and sign it in. Refused with 403 once any user exists.
| Field | Type | Description |
|---|---|---|
name | string | Display name |
email | string | Email address |
password | string | At least 8 characters, and whatever further rules a plugin such as Security adds |
// Response 201
{ "token": "eyJ...", "refreshToken": "eyJ...", "user": { "id": "...", "name": "...", "email": "...", "role": "super-admin" } }
// Error 400 - a missing field, a bad email address or a password the rules refuse
{ "error": "Password must be at least 8 characters" }
POST /api/auth/login
No authentication required. Limited to 5 attempts a minute per address.
Sign in with email and password. Usually answers with tokens. When a plugin adds a second step (two-factor from Security, for example) it answers with a challenge and a five-minute ticket instead, which is completed at /api/auth/login/verify.
| Field | Type | Description |
|---|---|---|
email | string | User email |
password | string | User password |
// Response 200 - signed in
{ "token": "eyJ...", "refreshToken": "eyJ...",
"user": { "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "additionalRoles": [], "level": 1 } }
// Response 200 - a second step is needed
{ "challenge": { "kind": "code", "title": "Two-factor code", "label": "Code", "inputMode": "numeric", ... }, "ticket": "eyJ..." }
// Error 401
{ "error": "Invalid credentials" }
A successful sign-in also sets a session cookie, used only so the public site can recognise a signed-in visitor (role-gated pages, draft previews). No API route accepts it in place of the Bearer token.
POST /api/auth/login/verify
No authentication required. Provide the ticket from /api/auth/login.
Answer a sign-in challenge. Five wrong answers end the ticket; sign in again.
| Field | Type | Description |
|---|---|---|
ticket | string | The ticket from the login response |
response | string | The code (or recovery code) |
mode | string | code (default) or recovery |
trust | boolean | Remember this browser, where the challenge offers it |
// Response 200 - the same as a successful /api/auth/login
// Error 401
{ "error": "...", "triesLeft": 4 }
POST /api/auth/refresh
No authentication required. Provide a valid refresh token.
Exchange a refresh token for a new access token. Refused once the session behind it has ended (signed out, a password change, or the account made inactive).
// Request body
{ "refreshToken": "eyJ..." }
// Response 200
{ "token": "eyJ..." }
// Error 401
{ "error": "This session has ended. Sign in again." }
POST /api/auth/logout
No authentication required. Safe to call without a token.
Ends the session the refresh token belongs to (sessions are stored on disk, so this survives a restart) and clears the session cookie.
// Request body (optional)
{ "refreshToken": "eyJ..." }
// Response 200
{ "ok": true }
GET /api/auth/me
Requires Bearer token.
Your own account, with your profile fields (the user-profiles collection).
// Response 200
{ "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "additionalRoles": [],
"isActive": true, "profile": { "phone": "..." } }
PUT /api/auth/me
Requires Bearer token.
Update your own name, email, password or profile (My Profile). A new password is checked against the site's password rules and ends your other sessions. Your role cannot be changed here.
// Request body - any of
{ "name": "Alice B", "email": "alice@example.com", "password": "...", "profile": { "phone": "..." } }
// Response 200 - the updated account, as GET /api/auth/me
// Error 409 - the email is already used by another account
POST /api/auth/me/avatar
Requires Bearer token.
Upload your picture (multipart, one file; JPEG, PNG, WebP or GIF, up to 8 MB). It is stored as a 256 px WebP. DELETE /api/auth/me/avatar removes it. Pictures are served at GET /api/auth/avatar/:name.
GET /api/auth/permissions
Requires Bearer token.
What you may do: the permissions of every role you hold combined (a bare name such as pages means every action on it; resource.action one action), whether your roles confine you to a few admin screens (adminScope, null for the whole admin), and the screen the admin opens on.
// Response 200
{ "permissions": ["pages", "media", "contacts.read", ...], "adminScope": null, "adminHome": "#/" }
GET /api/auth/permissions-registry lists every permission the role editor can grant, including those added by plugins.
GET /api/auth/sessions
Requires Bearer token.
Your sessions - where and when you are signed in - with the one you are using marked current. DELETE /api/auth/sessions/:sid ends one; POST /api/auth/sessions/revoke-others signs you out everywhere else.
// Response 200
[ { "sid": "...", "createdAt": "...", "lastUsedAt": "...", "expiresAt": "...", "ip": "...", "ua": "...", "current": true } ]
// POST /api/auth/sessions/revoke-others - Response 200
{ "ok": true, "ended": 2 }
POST /api/auth/forgot-password
No authentication required. Limited to 3 requests an hour.
Ask for a password reset email. It answers at once and the same way whether or not the address has an account; the email is sent afterwards, and at most 3 an hour go to any one account. The link is built on the Site URL (Site Settings, General).
// Request body
{ "email": "alice@example.com" }
// Response 200
{ "ok": true, "expiresIn": "1 hour" }
POST /api/auth/reset-password/check
No authentication required. Provide the token from the reset link.
Is the link still good, and (with a password) what the password rules make of a new password as it is typed.
// Request body
{ "token": "...", "password": "optional" }
// Response 200
{ "email": "alice@example.com", "expiresAt": "...", "minutesLeft": 42, "problems": [] }
// Error 400
{ "error": "Invalid or expired reset link", "expired": true }
POST /api/auth/reset-password
No authentication required. Provide the token from the reset link.
Set a new password. The link is used up, every session of the account ends, and the account holder is emailed that the password changed.
// Request body
{ "token": "...", "password": "new password" }
// Response 200
{ "ok": true }
// Error 400
{ "error": "Invalid or expired reset link", "expired": true }
Administrators send, copy or withdraw reset links for other users with the Users API.
On this page
- GET /api/auth/setup-status
- POST /api/auth/setup
- POST /api/auth/login
- POST /api/auth/login/verify
- POST /api/auth/refresh
- POST /api/auth/logout
- GET /api/auth/me
- PUT /api/auth/me
- POST /api/auth/me/avatar
- GET /api/auth/permissions
- GET /api/auth/sessions
- POST /api/auth/forgot-password
- POST /api/auth/reset-password/check
- POST /api/auth/reset-password