Authentication
This page is the operator-side authentication reference, the knobs you turn on the server to control how people sign in. The client-side reference ("how do I actually authenticate an API call?") is in API Authentication.
Authentication flows RomM supports:
- Username + password (default): local account, bcrypt-hashed, stored in the DB
- OIDC: single sign-on via an external IdP
- Client API Tokens: long-lived per-user tokens for companion apps and scripts
- Device pairing: short codes for bootstrapping a token onto a handheld
- Kiosk mode: unauthenticated read-only access, handy for public demos
Session config
Sessions are cookie-based and stored in Redis. Relevant env vars:
| Variable | Default | What it controls |
|---|---|---|
ROMM_AUTH_SECRET_KEY |
(required) | HS256 signing key for session tokens and JWTs. Generate with openssl rand -hex 32. Never rotate this casually, because it invalidates every active session and every outstanding invite link. |
ROMM_AUTH_SECRET_KEY_FILE |
Read the secret from a file (e.g. via Docker secrets) instead of an env var. | |
SESSION_MAX_AGE_SECONDS |
86400 (24 h) | How long a session cookie lives before the user has to sign in again. |
OAUTH_ACCESS_TOKEN_EXPIRE_SECONDS |
900 (15 min) | Short-lived OAuth2 access token TTL. |
OAUTH_REFRESH_TOKEN_EXPIRE_SECONDS |
2592000 (30 d) | Refresh token TTL for OAuth2. |
DISABLE_CSRF_PROTECTION |
false |
Disable CSRF middleware. Only do this behind a trusted reverse proxy that strips unwanted cross-origin traffic. |
Local (username + password)
Local accounts are created via invitations, registration, admin setup, or the Setup Wizard, and passwords are bcrypt-hashed.
Disable local password login entirely (force OIDC-only):
Keep a way in
Before setting DISABLE_USERPASS_LOGIN=true, confirm that at least one Admin account can sign in via OIDC. If OIDC breaks and you've already disabled local login, your only way in is editing the container env.
Admin-triggered password reset
Until email-based self-serve reset lands, admins set passwords manually for any user. The next login on that account will use the new password but existing sessions remain valid until they expire.
OIDC
See OIDC Setup for the full walkthrough. One-liner config sketch:
environment:
- OIDC_ENABLED=true
- OIDC_PROVIDER=keycloak
- OIDC_CLIENT_ID=...
- OIDC_CLIENT_SECRET=...
- OIDC_SERVER_APPLICATION_URL=https://auth.example.com
- OIDC_REDIRECT_URI=https://demo.romm.app/api/oauth/openid
When OIDC is configured, an OIDC sign-in option is offered alongside username/password. Set OIDC_AUTOLOGIN=true to redirect straight to the IdP without the user having to choose.
Client API Tokens
For anything long-lived (a companion app, a cron job, a script) use Client API Tokens instead of storing a password. Each token:
- Belongs to a specific user
- Carries a subset of that user's scopes (you choose which at creation time)
- Has an optional expiry (no expiry = never expires until manually revoked)
- Can be "paired" to a device via a short code
Each user gets up to 25 active tokens. The API side ("how do I send this thing in a request?") lives in API Authentication.
Kiosk mode
Grants unauthenticated, read-only access to nearly every GET endpoint. Anyone reaching the instance can browse but only a logged-in admin can write, scan, upload, or manage users.
Appropriate for:
- Shared-terminal demos
- Public-facing "display" instances
demo.romm.app
Authenticated users (when you do sign in) will still see the full UI with all actions available to their role.
Download-endpoint auth bypass
Skips auth on GET /api/roms/{id}/content/… and the firmware download endpoint. Exists so third-party apps that can't carry a bearer header (like dumb emulators loading a ROM by URL) can still pull files. Only enable this when the public internet can't reach RomM directly, i.e. there's auth or an IP allowlist at the reverse-proxy layer. Otherwise you've just made your library world-downloadable.
Revoking access
To fully cut a user off, an admin needs to:
- Revoke all of the user's Client API Tokens.
- Disable or delete the user account.
Disabling preserves the account row (useful for audit/future re-enable). Deletion removes the user record and personal data, with the caveats covered in Users & Roles → Editing and deleting users.