Auth (REST)
Atomo Server provides JWT‑based auth with sessions stored in Postgres.
Environment
JWT_SECRET— HMAC secret for signing tokens (required in production)PASSWORD_MIN_LENGTH— default 8PASSWORD_REQUIRE_COMPLEXITY— require letters and numbers (default true)ADMIN_EMAIL/ADMIN_PASSWORD— bootstrap an admin user on server start
Admin bootstrap
- On startup the server ensures platform tables exist and, if
ADMIN_EMAILandADMIN_PASSWORDare both set, creates an admin user when that email does not already exist. - The user is created with a ULID id, the
adminrole, and an argon2id password hash. - Create-once, keyed by email. Restarting does not duplicate the user, and an existing admin's password is not updated from
ADMIN_PASSWORD. Two consequences to know:- Changing
ADMIN_PASSWORDand restarting is otherwise a silent no-op (the old password keeps working). The server now logs aWARNwhen the env password differs from the stored one. To actually rotate it on boot, setADMIN_RESET_PASSWORD=true(a one-shot that updates the existing admin's hash fromADMIN_PASSWORD); unset it again afterward. - Changing
ADMIN_EMAILseeds an additional admin (the old one remains). Remove stale admins manually if that's not intended.
- Changing
Endpoints
http
POST /auth/login
Content-Type: application/json
{ "email": "user@example.com", "password": "secret" }Response
json
{ "token": "<jwt>", "refresh_token": "<refresh>", "user": { "id": "...", "email": "...", "role": "viewer" } }Self-registration (optional, default off)
POST /auth/registeris mounted only whenATOMO_ENABLE_SELF_REGISTRATION=true; otherwise the route does not exist (404). The platform never exposes an open sign-up surface by default.- Provisioning is configurable so the platform imposes no convention:
ATOMO_SELF_REGISTRATION_ROLE— role granted to new users (defaultviewer).ATOMO_SELF_REGISTRATION_TENANT—none(default; unscopedtenant_id = NULL),per-user/self(each user is its own tenant), or any other value used as a fixed shared tenant id.
- Duplicate emails are race-safe: registration relies on the
users.emailunique constraint, so a duplicate returns409 Conflictrather than creating a second user.
http
POST /auth/register # only when ATOMO_ENABLE_SELF_REGISTRATION=true
Content-Type: application/json
{ "email": "user@example.com", "password": "secret123", "firstName": "A", "lastName": "B" }Response (same shape as login; role/tenant follow the configured policy)
json
{ "token": "<jwt>", "refresh_token": "<refresh>", "user": { "id": "...", "email": "...", "role": "viewer", "tenant_id": null } }http
POST /auth/logout
Authorization: Bearer <jwt>http
POST /auth/refresh
Content-Type: application/json
{ "refreshToken": "<refresh>" }Response
json
{ "token": "<jwt>", "refresh_token": "<refresh>", "user": { "id": "...", "email": "...", "role": "viewer" } }http
GET /auth/me
Authorization: Bearer <jwt>OAuth2/OIDC SSO
- Providers are configured via env vars and auto-discovered at startup:
google,github,microsoft,okta. - Per provider, set
OAUTH_<PROVIDER>_CLIENT_ID,_CLIENT_SECRET,_AUTH_URL,_TOKEN_URL,_USERINFO_URL, and optionally_REDIRECT_URI.
http
GET /auth/oauth/providers # list configured providers
GET /auth/oauth/authorize?provider=google # redirect to provider
GET /auth/oauth/callback/{provider}?code=...&state=... # find-or-create user, returns JWTCallback response
json
{ "access_token": "<jwt>", "user": { "id": "...", "email": "...", "role": "viewer" } }Notes
- Password hashing uses argon2id; existing bcrypt hashes are still verified for seamless migration.
- In production, set
JWT_SECRET(server refuses to start without it whenATOMO_ENV=production). - Include
Authorization: Bearer <jwt>for protected routes. - Access tokens expire (~24h). Use
POST /auth/refreshwith a valid refresh token to rotate both. - New OAuth users are created with the
viewerrole by default.