Authentication
Password authentication#
Local accounts authenticate with username and password:
- Passwords are hashed with bcrypt (cost factor configurable via
ABRQ_BCRYPT_ROUNDS, default 12). Plaintext passwords are never stored or logged. - Forced first-login change. A freshly seeded account must change its password before it can use the application — the UI blocks every other screen until the change completes.
- Login attempts are rate-limited to 10 per minute per client (on top of the global API limit of 100 per minute).
Initial admin seed#
On first boot against an empty users table, the backend seeds one
administrator account from ABRQ_INITIAL_ADMIN_USERNAME,
ABRQ_INITIAL_ADMIN_EMAIL, and ABRQ_INITIAL_ADMIN_PASSWORD. Two guardrails
apply:
- In production, startup refuses to run with the literal default password — you must set a real one.
- The seeded password is also written once to a
0600-permission file (ABRQ_INITIAL_PASSWORD_FILE, default/var/abrq-dip/initial-password.txt) so the bootstrap credential never has to travel through logs or chat. This is the only file the application ever writes credentials to.
JWT session model#
Interactive sessions use two tokens with different lifetimes and transports:
| Token | Lifetime | Transport |
|---|---|---|
| Access token | 15 minutes (ABRQ_ACCESS_TOKEN_TTL_MINUTES) |
Authorization: Bearer header |
| Refresh token | 7 days (ABRQ_REFRESH_TOKEN_TTL_DAYS) |
HTTP-only cookie |
- Tokens are signed with
ABRQ_JWT_SECRET(HMAC;ABRQ_JWT_ALGORITHMsupports HS256/HS384/HS512, default HS256). Production requires a secret of at least 32 characters. - The refresh token lives in the
abrq_refreshcookie:HttpOnly,SameSite=Strict, and path-scoped to/api/v1/auth— it is never sent to any other endpoint and is unreadable from JavaScript. - Rotation with reuse detection. Every refresh issues a new refresh token
and invalidates the old one. If a previously used refresh token is presented
again — the signature of token theft — the platform revokes the entire
session family, forcing a fresh login, and records a
refresh_token_reuse_detectedevent in the audit log. - Revoked tokens go on a server-side deny-list that is checked on every request; expired deny-list entries are pruned by the retention sweep.
Note. Rotating
ABRQ_JWT_SECRETinvalidates all outstanding sessions at once — a deliberate kill switch, independent of the secrets master key. See Secrets & encryption.
API tokens#
For scripts and integrations, users create personal API tokens under Settings → Tokens (admins manage any user's tokens):
- Tokens carry the
abrq_prefix so they are recognizable in configs and secret scanners. The full value is shown once at creation and never again. - Each token has a scope:
readorwrite, enforced centrally — a read-scoped token receives 403 on every mutating request, regardless of the user's roles. - Tokens can additionally be scoped to specific projects, limiting them to a subset of the ETL surface.
- Tokens authenticate via the same
Authorization: Bearerheader as access tokens and can be revoked individually at any time.
SSO and LDAP#
Abrq DIP supports external identity at a concepts level here — admin setup is covered in authentication providers:
- OIDC single sign-on. Admins register one or more OIDC providers; the
provider configuration (client secret included) is stored encrypted. The
login flow is the standard authorization-code dance: the backend issues a
signed state JWT with a 600-second TTL (
ABRQ_SSO_STATE_TTL_SECONDS) at the start of the flow and verifies it at the callback, defeating replayed or forged callbacks. - LDAP. Admins register LDAP providers whose bind configuration is stored encrypted; users authenticate with their directory credentials.
The SSE exception#
Two endpoints stream live logs over Server-Sent Events. Browsers cannot attach
headers to EventSource connections, so these two endpoints alone accept
the access token as a ?token= query parameter instead of a header:
GET /api/v1/cdc-tables/{id}/logs/streamGET /api/v1/streams/{id}/runs/stream
The token is the same short-lived (15-minute) access token — the query-string exposure window is bounded by that TTL.
Unauthenticated surface#
Only the following endpoints accept requests without credentials: /health,
/metrics, /docs, /openapi.json, POST /api/v1/auth/login,
POST /api/v1/auth/refresh, and the SSO discovery/start/callback routes.
Everything else requires a valid access token or API token.