effect-auth

Sessions

Understand effect-auth session tokens, assurance, refresh, rotation, and revocation.

A session is durable proof that an authentication ceremony completed for a user. It is not a profile or application user: map validated userId to your domain and, at a trusted server boundary, to PermissionSubject.user(userId) and CurrentPrincipal when using permission policy.

Token and row

The browser receives an opaque sessionId.secret cookie; storage keeps the row and only an HMAC-SHA-256 secretHash. A database read alone therefore does not reveal a usable bearer token.

DataLocationPurpose
sessionIdCookie, row, current/list responsesSelect the authoritative row
Secret / secretHashRaw only in cookie; hash only in rowProve possession
userIdRow and trusted responsesMap technical session to application identity
Authentication eventsRow onlyDerive assurance, method/factor freshness, and provenance
authTime, aal, amr, mfaVerifiedAtRow and public session summariesGuards and browser UX
Creation/activity/expiry timestampsRow; selected values in current/list responsesRefresh, expiry, account session management
Metadata and request contextRow; active-session list responseDevice/IP/location UI when the application deliberately records it

Default cookie settings are __Host-session, Secure, HttpOnly, SameSite=Lax, and Path=/. The session, trusted-device, and login-approval profiles optionally accept explicit lax, strict, or none and safely default to lax; the email process profile is fixed lax; OAuth flow is Lax for omitted/query response mode and None for validated form_post. Every maintained sensitive profile is a Secure, HttpOnly, host-only __Host- cookie with Path=/ and no Domain. JavaScript should never read or persist these credentials.

AuthDomainConfigLive({ sessionCookie }) configures only the session cookie by design. It is not ambient security discovery and does not change trusted-device, login-approval, email-process, or OAuth-flow profiles.

Lifecycle

Refresh preserves the bearer secret; assurance rotation replaces it
TransitionStorage/token effect
IssueCreate row and return IssuedSession; standard HTTP Operations commit the cookie
ValidateBound syntax, authenticate the hash-only snapshot, then load and validate the full row
RefreshCAS the exact active hash/evidence snapshot; preserve the secret and absolute-expiry boundary
Assurance rotationCompare-and-set evidence and secret atomically; one concurrent transition wins
Revoke/logoutMark server row revoked; clear current cookie where applicable

The default policy is 30-day idle TTL, 90-day absolute TTL, and one-day refresh threshold. All three durations must be positive and finite, refreshAfter <= idleTtl <= absoluteTtl, and absolute TTL cannot exceed 365 days. A per-create override must obey the same ordering and cannot exceed the service's configured absolute TTL. Invalid overrides fail with SessionCreateError before random generation or storage. Effective expiry is the earlier of idle expiry and authTime + absoluteTtl.

makeSessions is effectful and SessionsLive validates configuration while its Layer is built. The session HMAC key must contain 32 through 1024 UTF-8 or raw bytes. Generated session IDs use 16 random bytes by default and accept 16 through 128; bearer secrets use 32 by default and accept 16 through 128. Invalid startup options fail with SecurityConfigurationError before crypto or store work, and every bearer rotation reuses the validated configured secret-byte count.

Session construction descriptor-safely snapshots its exact dependency envelope before validation. The service retains detached validated key and TTL/configuration values, captured crypto and store callbacks, and one frozen custom-evidence registry facade. prepareCreate, create, validation, rotation, and the public customEvidencePolicies property all use that same registry; later caller mutation cannot change or raise session assurance. Atomic TOTP and recovery-code rotation constructors apply the same rules and expose their captured registry.

Bearer validation rejects malformed and oversized tokens before HMAC or storage. A syntactically valid token is HMACed, compared against SessionStore.findBearerById, and only a matching bearer can trigger a full-row read. Core compares the same digest with the full row again to close secret-rotation races, checks revocation and expiry, and only then evaluates persisted evidence and custom assurance policy. Unknown IDs, wrong secrets, revoked rows, and expired rows therefore remain generic unauthenticated outcomes; storage, crypto, and valid-bearer integrity failures remain operational errors.

Validation recomputes assurance summaries from stored events and treats inconsistent rows as an internal integrity failure. Refresh performs the same bearer authentication and then compare-and-sets the exact secret hash, authentication events, active state, and expiry at the store boundary, including when no activity extension is due. A concurrent rotation or revocation makes refresh fail instead of returning an invalid old token. Direct Sessions.rotate(sessionId) is an administrative primitive with no presented-token, expiry, or revocation precondition; do not expose it as a bearer-driven endpoint.

Revocation takes effect on the next validation even if the browser retains its cookie. Standard logout and explicit current-session revocation do not require step-up and clear the current cookie. Revoking another session or calling revokeOthers enforces the 15-minute sensitiveActionStepUpPolicy, which also rejects sessions carrying recovery_remediation; revokeOthers preserves the current row. Target revocation also verifies ownership. Listing returns active sessions ordered by recent activity.

Assurance context

Core derives summaries from authentication events; callers do not select them.

SummaryBuilt-in meaning
aal1Verified basic authenticator or channel
aal2Verified strong authenticator or independent second factor; UV passkey or TOTP can establish it
aal3Explicit custom hardware-bound policy only; no built-in flow establishes it
amrCanonical methods such as pwd, email_otp, magic_link, oauth, totp, passkey, recovery_code

Recovery-code evidence creates constrained remediation, not ordinary aal2. These are project-local tiers and do not by themselves claim NIST SP 800-63 conformance.

Cloudflare account-security UI

With Alchemy routing /auth/* through the same app origin, the browser client can render a device/session screen without accessing the cookie:

import { createAuthClient } from "@effect-auth/core/Client";

const auth = createAuthClient();
const { sessions } = await auth.session.list();

for (const session of sessions) {
  console.log(session.current, session.ip, session.userAgent, session.city);
}

currentOrUndefined() converts only the typed unauthenticated result to undefined; network, decode, and internal failures still reject. refresh, revoke, revokeOthers, and logout support the rest of the standard account-security flow. Only record and expose request metadata appropriate for your privacy policy.

Prefer standard session HTTP Operations for cookie reading, public errors, origin protection on mutations, user scoping, and response commitment. Direct Sessions or SessionCookie use transfers those transport decisions to the application. Continue with Browser Client, Step-up, and Security Policies.

On this page