4.3 KiB
Auth
Three auth modes, configured via AUTH_MODE env var. All three issue the
same JWT after authentication — downstream middleware is auth-mode-agnostic.
Builtin Auth (default)
Username/password authentication with bcrypt hashing.
Register
POST /auth/register
{
"username": "jdoe",
"password": "...",
"email": "jdoe@example.com",
"display_name": "Jane Doe"
}
Gated by allow_registration policy. Returns same shape as Login.
A unique handle is auto-generated from username (collision-safe
with -2, -3 suffixes).
Login
POST /auth/login
{ "username": "jdoe", "password": "..." }
Response:
{
"access_token": "eyJ...",
"refresh_token": "opaque-string",
"user": {
"id": "uuid",
"username": "jdoe",
"email": "jdoe@example.com",
"display_name": "Jane Doe",
"handle": "jdoe",
"role": "user",
"auth_source": "builtin",
"avatar_url": "/api/v1/profile/avatar?v=1234",
"created_at": "...",
"last_login": "..."
}
}
Refresh
POST /auth/refresh
{ "refresh_token": "opaque-string" }
Returns new access_token and refresh_token. Old refresh token is
invalidated (rotation).
Logout
POST /auth/logout
{ "refresh_token": "opaque-string" }
Revokes the refresh token. Returns { "message": "logged out" }.
mTLS Auth
Mutual TLS via reverse proxy headers. The proxy terminates TLS and forwards cert DN fields in headers. The backend auto-provisions users on first connection.
Configured by: AUTH_MODE=mtls + MTLS_HEADER_* env vars.
No explicit login/register endpoints. User identity is extracted from
the TLS certificate on every request. password_hash is NULL for
mTLS users.
Headers parsed:
MTLS_HEADER_CN(default:X-SSL-Client-CN) — common name → usernameMTLS_HEADER_DN(default:X-SSL-Client-DN) — distinguished name → metadataMTLS_HEADER_VERIFY(default:X-SSL-Client-Verify) — must beSUCCESSMTLS_HEADER_FINGERPRINT(default:X-SSL-Client-Fingerprint) — stable identity
On first request with a valid cert, the system creates a user with
auth_source=mtls and external_id=fingerprint.
OIDC Auth
OpenID Connect authorization code flow. Tested with Keycloak but compatible with any OIDC-compliant IdP.
Configured by: AUTH_MODE=oidc + OIDC_* env vars.
OIDC Login (redirect)
GET /auth/oidc/login
Redirects to the IdP authorization endpoint. Stores state + nonce
in oidc_auth_state table (cleaned up after callback).
OIDC Callback
GET /auth/oidc/callback?code=...&state=...
Exchanges authorization code for tokens, validates ID token signature
via JWKS, extracts claims. Auto-provisions user on first login with
auth_source=oidc and external_id=sub. Returns HTML fragment that
passes tokens to the frontend via URL fragment.
Claim mapping:
sub→external_idpreferred_username→username+handleemail→emailname→display_namegroups→ synced to internal groups withsource=oidc
Split-horizon issuer: OIDC_EXTERNAL_ISSUER_URL can differ from
OIDC_ISSUER_URL when the IdP's internal address (Docker/K8s service)
differs from its external address.
OIDC env vars:
| Var | Required | Description |
|---|---|---|
OIDC_ISSUER_URL |
Yes | IdP issuer URL for discovery |
OIDC_EXTERNAL_ISSUER_URL |
No | External issuer (if different from internal) |
OIDC_CLIENT_ID |
Yes | Client ID |
OIDC_CLIENT_SECRET |
Yes | Client secret |
OIDC_REDIRECT_URL |
No | Callback URL (auto-derived if not set) |
Login Page Adaptation
The /login page adapts by AUTH_MODE:
| Mode | UI |
|---|---|
builtin |
Username/password form + register link |
mtls |
Certificate status display |
oidc |
"Sign in with SSO" button |
User Identity Fields
All auth modes produce users with:
| Field | Description |
|---|---|
auth_source |
builtin, mtls, or oidc |
external_id |
IdP subject (OIDC) or cert fingerprint (mTLS). NULL for builtin. |
handle |
Unique @mention handle. Auto-generated, collision-safe. |
handle is the canonical identifier for @mentions (replacing username
in the resolution chain since v0.24.0).