Externally via gateway at /api/login, /api/signup, etc.
Full URL from browser: https://host/api/login → gateway strips /api → auth service gets /login.
- User submits credentials to
POST /loginorPOST /signup - Service responds with
Set-Cookie: session_token=<token>; HttpOnly; Path=/; Max-Age=2592000 - Gateway automatically extracts this cookie and validates via
POST /validate - On success, gateway injects
X-User-Idheader to downstream services POST /logoutclears the cookie
| Header | Required? | Source | Description |
|---|---|---|---|
X-Session-Token |
for /logout, /validate |
Cookie extraction (gateway) | Session token |
X-User-Id |
for /account |
Gateway (from validation) | User's snowflake ID |
{ "error": "error_code", "message": "Human readable message" }| Status | error_code | Meaning |
|---|---|---|
| 401 | account_not_found |
Account does not exist |
| 401 | session_not_found |
Session expired or invalid |
| 401 | invalid_password |
Wrong password |
| 404 | user_not_found |
User not found |
| 409 | username_exists |
Username already taken |
| 500 | password_hash_failed |
Password processing error |
Body:
{ "username": "alice", "password": "hunter2" }Response 204 No Content.
Sets cookie: session_token=<hex>; HttpOnly; Path=/; Max-Age=2592000; SameSite=Lax
(Adds Secure flag in non-DEV environments)
Errors: account_not_found (401), invalid_password (401)
Body: same as login.
Response 204 No Content.
Sets same cookie as login (auto-login after signup).
Errors: username_exists (409)
Header: X-Session-Token: <token>
Response 204 No Content.
Sets Set-Cookie: session_token=; Max-Age=0 (clears cookie).
Header: X-Session-Token: <token>
Response 200:
{ "user_id": "12345" }user_id is null if token is expired or invalid.
Response 200:
{
"username": "alice",
"is_admin": false,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-06-11T12:00:00Z"
}- Argon2id algorithm
- Random salt per invocation
- Hashed and verified server-side only