Session Management
Surge issues cookie-based sessions for browser-authenticated users and token-based sessions for programmatic access. Sessions have a configurable time-to-live and can be revoked individually or in bulk.
Session creation
Sessions are minted when a login flow completes successfully or when a service with direct_auth grant authenticates a user programmatically. The engine produces an IssuedSession containing both the internal session ID and the plaintext token — this plaintext is returned exactly once.
# Browser: login flow completion sets session cookie automatically
# Programmatic: direct auth returns session token
curl -X POST http://localhost:3000/v1/authenticate/password \
-H "Authorization: Bearer aeg_svc_..." \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "correct-horse-battery-staple"}'{
"session": { "id": "018f9a1b-...", "token": "aeg_s_1a2b3c4d5e6f7g8h" }
}If you lose the token, you cannot recover it — the database stores only the SHA-256 hash. Revoke the session and issue a new one.
Token format
Session tokens are generated by SessionToken::generate():
// 128-bit random → base62-encoded → "aeg_s_" prefix → 22-char padded
let token = SessionToken::generate();
// Example: "aeg_s_1a2b3c4d5e6f7g8h9i0j"| Property | Value |
|---|---|
| Entropy | 128 bits (cryptographic RNG) |
| Encoding | Base62 (alphanumeric) |
| Prefix | aeg_s_ |
| Length | 22 characters (zero-padded) |
| Storage | SHA-256 hash only |
The aeg_s_ prefix helps identify session tokens in logs and traffic, distinct from service tokens (aeg_svc_) and flow IDs (aeg_f_).
Cookie handling
For browser endpoints, Surge sets a session cookie on successful login flow completion. Cookie behavior is configured via environment variables:
| Setting | Default | Description |
|---|---|---|
SURGE_COOKIE_DOMAIN | .panit.dev | Cookie domain (leading dot = subdomains) |
SURGE_SESSION_TTL_HOURS | 72 | Session time-to-live |
# Override domain for your deployment
export SURGE_COOKIE_DOMAIN=.example.com
export SURGE_SESSION_TTL_HOURS=24The Set-Cookie header includes:
- Domain: from
SURGE_COOKIE_DOMAIN(shared across subdomains for SSO) - Path:
/— valid for all routes under the auth mount - HttpOnly: true — inaccessible to JavaScript
- SameSite: Lax — sent on top-level navigation, not cross-site subrequests
- Secure: true (when deployed over HTTPS)
Session verification
Browser-based (cookie)
Sessions are automatically extracted from the cookie on browser endpoints (/whoami, /logout). The incoming cookie value is hashed and compared against the database — the plaintext token is never stored.
Service-to-service (API)
Backend services verify sessions by sending the token via the verify endpoint:
curl -X POST http://localhost:3000/v1/sessions/verify \
-H "Authorization: Bearer aeg_svc_..." \
-H "Content-Type: application/json" \
-d '{"token": "aeg_s_1a2b3c4d5e6f7g8h"}'Verification checks three conditions:
revoked_at IS NULL— session hasn't been explicitly revokedexpires_at > now()— session hasn't expired- The identity is in
Activestate
If all three pass, the response includes the identity and session metadata. If any fail, the token is rejected.
{
"identity": { "id": "018f9a1b-...", "username": "alice" },
"session": { "id": "...", "expires_at": "2026-07-11T12:00:00Z" }
}Session revocation
Single session
Revoke a specific session by its token. Only the SHA-256 hash is needed — you can revoke a session even if you only have the hashed token stored:
# Revoke by token hash (service with revoke grant)
curl -X POST http://localhost:3000/v1/sessions/revoke \
-H "Authorization: Bearer aeg_svc_..." \
-H "Content-Type: application/json" \
-d '{"token": "aeg_s_1a2b3c4d5e6f7g8h"}'Bulk revocation
Revoke all sessions for a specific identity — useful for "log out everywhere" or when disabling an account:
curl -X POST http://localhost:3000/v1/identities/{id}/revoke-sessions \
-H "Authorization: Bearer aeg_svc_..."This sets revoked_at on every active session belonging to that identity, immediately invalidating them.
Expiry and garbage collection
Session TTL
Sessions expire after the configured TTL (SURGE_SESSION_TTL_HOURS, default 72 hours). Expiry is checked at verification time — an expired session is rejected the same as a revoked one.
// Config is parsed from Duration (derived from SURGE_SESSION_TTL_HOURS)
let config = SessionConfig {
session_ttl: Duration::hours(72),
};Garbage collection
A background garbage collector periodically deletes sessions that are expired or revoked. This keeps the session table from growing unboundedly:
-- What the GC effectively does:
DELETE FROM session
WHERE expires_at <= now() OR revoked_at IS NOT NULL;GC runs on a configurable interval. Sessions that are still active remain in the table until their natural expiry or revocation.
Related: Login Flows, Password Authentication