pylon_) that maps to a Session { token, user_id, expires_at, device, created_at, tenant_id }. Bearer headers and HttpOnly cookies both resolve through the same SessionStore.
Because session tokens are opaque and server-resolved, they need no signing key and can be revoked immediately.
Session shape
Defaults
Sessions live in memory by default. That is fine for development, but users get logged out on every restart. For production, point Pylon at a SQLite file:
Refresh
Rotate a session’s token without breaking the user’s sign-in:startSessionAutoRefresh(intervalSeconds:) that does this automatically).
Revoke
Sign out the current device
Set-Cookie with an expired value).
Sign out everywhere
List active sessions
Cookies vs bearer tokens
Pylon supports both transports for the sameSession. Pick based on client type:
To enable cookie auth:
pylon_session. It’s automatically set on:
/api/auth/magic/verifysuccess/api/auth/password/loginsuccess/api/auth/password/registersuccess/api/auth/callback/:providersuccess (both GET and POST)
/api/auth/session DELETE.
When both a cookie and an Authorization: Bearer header are present on the same request, the bearer header wins.
Multi-tenant: switching organizations
For apps with workspaces/orgs, attach atenant_id to the session so policies like data.orgId == auth.tenantId can run automatically:
OrgMember { userId, orgId } row and returns 403 NOT_A_MEMBER if it does not exist. Clients cannot impersonate an org they do not belong to.
Pass null to leave the org (drop back to the lobby):
select-org, every request resolves to an AuthContext with tenantId set, and your row-scoped policies see it as auth.tenantId.
Guest sessions
For pre-login state (cart contents, theme preference, anonymous draft), mint a guest session:user_id (so their cart persists across page loads), but is_authenticated() returns false. AuthMode::User rejects them, so guests cannot access user-only routes.
When the user signs in for real, upgrade the guest session in place:
/api/auth/upgrade is admin-gated and exists for backfill scripts; normal upgrade should flow through magic-code verify or OAuth callback, which mint a fresh user session and consume the guest token.
Programmatic session creation (admin only)
In dev mode or with admin auth, mint a session for any user:FORBIDDEN. Use it for:
- Backfill scripts that need to sign in as a user
- Tests
- Impersonation features for support agents (gate on
auth.hasRole('support')in your wrapper)
Sweep expired sessions
Background cleanup runs automatically — every authenticated request checksexpires_at and removes the session if expired. For the SQLite-backed store, you can also trigger an explicit sweep:
Session storage backends
TheSessionStore accepts a pluggable SessionBackend:
- In-memory — default; lost on restart
- SQLite —
PYLON_SESSION_DB=pathenables it
SessionStore::with_backend. See crates/runtime/src/session_backend.rs for the SQLite reference impl.
Security defaults
- Tokens are 256-bit CSPRNG — un-guessable
- Constant-time token lookup — no timing leak on session resolution
HttpOnlycookies — JS can’t read the cookie via XSSSecurecookies in non-dev — refused over plain HTTPSameSite=lax— CSRF-resistant by default; switch tostrictif you don’t have cross-site sign-in flows- Sessions can be revoked individually or en masse — no JWT-style “until expiry, can’t kill” problem
/mereturns the runtime-resolved context, not a fresh DB lookup — admin-token requests show as admin even though they don’t have a session row