Session integrity

Every device is known and revocable

A stolen laptop, a forgotten phone in a taxi, a contractor whose access ended last week — every one of these is a session that must die at the moment we say it does. We treat device and session state as a first-class auditable boundary, not a side effect of cookies.

Executive summary

Every login is fingerprinted at a 128-bit truncated SHA-256 of source IP and user agent, tied to a session token that is itself stored as a hash. Even with a database dump, an attacker cannot replay an active session.

A new-device login fires a security event the user can see and revoke from any other session. "Log out everywhere" kills every active session in one transaction; revoked tokens close LiveView sockets in real time.

Our commitments

Five rules for the session boundary

The session boundary is the perimeter of every other security control. We hold it tightly.

01

Session tokens are hashed at rest

Even with full database read access, an attacker cannot impersonate an active session. The raw token only ever lives in the user's cookie.

02

Every new device produces an audit event

A login from an unrecognized fingerprint generates a :login_new_device event the user can review and act on — including from another session in real time.

03

Revocation is instant and audit-preserved

Revoking a device or session deletes the token but writes an immutable security event with the actor, time, and reason.

04

Sealing requires MFA, every time

Even a fully trusted device on a healthy session must re-prove identity before applying a professional seal. The seal is the moment that matters.

05

Trust is opt-in, never inferred

A device is 'known' the first time it logs in successfully. It only becomes 'trusted' when the user explicitly marks it. We never auto-elevate trust.

Implementation — device fingerprinting

How we identify a device

The fingerprint is a stable session-scoped hash, not a tracker. We do not collect MAC addresses, IMEIs, or hardware serials.

Fingerprint SHA-256(IP || UA) — truncated to 16 bytes Hex-encoded, deterministic per device per session origin
Storage known_devices table Unique constraint on (user_id, device_fingerprint)
Concurrency Atomic upsert on_conflict :nothing — race-safe under concurrent logins
Identification Browser + OS parse Friendly label like Chrome on macOS; raw UA never displayed
Trust state Boolean, opt-in trusted: true requires explicit user action
Deletion Soft delete deleted_at preserves audit trail; row is unrecoverable
New-device event :login_new_device Fires only on first appearance of a fingerprint
Last-seen tracking last_seen_at, second precision Updated on every successful auth

Implementation — session tokens

How sessions are stored and revoked

Token entropy 32 bytes (256 bits) Phoenix.Token-style random; signed cookie
At-rest storage SHA-256(raw_token) The raw token never reaches the database
Sync/streaming SHA-256(SHA-256(raw)) Double hash so client-side sync streams cannot leak a usable token
Revoke by hash get_session_by_token_hash/2 Operations look up sessions by hash; raw token never required
Bulk revoke revoke_all_sessions/1 Log out everywhere — single transaction, then close LiveView sockets
Selective revoke revoke_other_sessions/2 Log out other devices — preserves the current session only
Concurrent session limit max_concurrent_sessions Per-org policy; enforced at login. Default unset
Revocation event :session_revoked Records actor, target token's metadata, and reason

The full picture

What is built, what is being built, and what we chose not to build

Live today

SHA-256 device fingerprinting with race-safe upsert

Live

Concurrent logins from a new device cannot create duplicates; unique constraint on (user_id, device_fingerprint).

New-device security events

Live

A :login_new_device event fires exactly once per fingerprint, with IP and user agent metadata for review.

Hashed session tokens with double-hash for sync

Live

At rest: SHA-256(raw). Over Phoenix Sync streams: SHA-256(SHA-256(raw)). The client cannot reconstruct a usable token from any leak surface.

Log out everywhere / log out other devices

Live

Single-transaction bulk revocation; LiveView sockets close in real time as their tokens are killed.

Step-up MFA at sealing (10-minute sudo window)

Live

MFA enrollment is required before a user can seal. The first seal in a session prompts a fresh TOTP (or one-time backup code); subsequent seals within a 10-minute window pass through. The same gate applies to single-doc and batch seals — one verification authorizes the batch. Every gate outcome (blocked, failed, passed) is recorded in the hash-chained audit log.

Geolocation enrichment of login events

Live

Each session's source IP is geolocated; sudden country changes surface in the security event log.

Per-org max concurrent session policy

Live

Org admins can cap how many simultaneous sessions a single user may hold; enforced at login.

Building now

WebAuthn / FIDO2 passkeys

Building now

Device-bound public-key authentication that replaces TOTP for sealing. Phishing-resistant, no shared secret to steal.

Target: passkey enrollment in settings this quarter; sealing-gate integration next.

Step-up authentication for sensitive operations

Building now

Re-prompt for MFA or passkey on credential changes, payment method updates, and member-role escalation — not just on sealing.

Impossible-travel detection

Building now

Flag sessions whose source location moved faster than a commercial flight could explain. Triggers a forced re-auth instead of a hard kick.

Roadmap

Hardware key requirement (Enterprise policy)

Roadmap

Org admins can require a FIDO2 hardware key for any sealing operation, optionally limiting which key vendors are accepted.

Driven by Enterprise customers with existing hardware-key fleets.

Device attestation

Roadmap

Verify a managed device's identity (TPM-backed key on Windows, Secure Enclave on Apple) before granting access.

Continuous session re-evaluation

Roadmap

Periodic background re-checks of session posture (geo, risk score, device trust); inactive sessions auto-tier down.

Session export for SIEM ingestion

Roadmap

Push login and session events to your SIEM (Splunk, Datadog, Sumo) over a signed webhook stream.

Considered & rejected

Browser-fingerprint tracking (canvas, fonts, WebGL)

Considered & rejected

A SHA-256 of IP + UA is a session-scoped identifier the user can break by clearing cookies. We do not want to know more than that.

Why we rejected it: high-entropy fingerprints cross the line from "is this the same session" to "is this the same person across browsers and incognito." That is tracking, not security. We are a sealing platform; we do not need it.

MAC address / IMEI / hardware serial as device ID

Considered & rejected

These cannot be revoked, often cannot be rotated, and survive factory resets. A leaked IMEI is forever.

Why we rejected it: hardware identifiers are an attractive nuisance. They look stable, but they create a permanent record that follows the user forever and creates HIPAA / GDPR risk we do not need.

SMS-based MFA

Considered & rejected

SS7 attacks, SIM swapping, and carrier-side inspection make SMS the weakest second factor in common use.

Why we rejected it: SMS is broken for security, full stop. NIST has been deprecating SMS-as-second-factor since 2016. We use TOTP today and are moving to passkeys; SMS is not on the path.

Aggressive geofencing as a hard block

Considered & rejected

A licensed engineer in Texas legitimately travels to a conference in Germany. Hard-blocking on country mismatch generates more support tickets than security wins.

Why we rejected it: we surface geo signals (impossible-travel, sudden country change) but trigger step-up auth, not termination. False positives in security have a cost — we measure it and design around it.

Persistent "remember this device for 90 days" MFA bypass

Considered & rejected

Long-lived MFA bypasses are the most common path to account takeover. We do not offer them, even though competitors do.

Why we rejected it: every "remember me" is a prefilled answer to "did you actually verify the human?" For sealing — a legally significant act — we accept the friction of MFA every time. The friction is the feature.

Compliance mappings

Controls this surface satisfies

SOC 2 CC6.1

Logical Access — Authentication

Session MFA; hashed session tokens; bound device fingerprints

SOC 2 CC6.2

Logical Access — User Access Management

Per-user device list with explicit trust + revoke surfaces

SOC 2 CC6.3

Logical Access — Termination

Single-transaction bulk session revocation; instant socket teardown

SOC 2 CC7.2

System Operations — Anomaly Detection

New-device events, geolocation enrichment, impossible-travel signals

ISO 27001 A.9.2.6

Removal or adjustment of access rights

Audit-preserved soft-delete on devices and sessions

ISO 27001 A.9.4.2

Secure log-on procedures

MFA, session token hashing, fingerprint-based new-device alerts

HIPAA §164.312(a)(2)(iii)

Automatic Logoff

Per-org max-session policy; idle session expiry configurable

NIST SP 800-63B AAL2

Authentication Assurance Level 2

TOTP today; passkeys (AAL3-eligible) shipping this quarter

For compliance teams

Questions you don't need to call to ask

What's the maximum session lifetime?
Session tokens have a 14-day hard cap. The per-org idle-timeout policy defaults to 1440 minutes (24 hours); a 30-minute fallback applies to organizations outside the Enterprise tier. Sealing always requires an active MFA-verified session regardless of session age.
What happens to a user's sessions when they're removed from an org?
Removing a user from an organization revokes every session they hold for that organization in a single transaction. LiveView sockets close immediately. The user's account and personal sessions outside that org are not affected.
Can a contractor's access be time-bounded?
Yes. Membership records support an expires_at; the session lookup checks this on every request. Expired memberships fail the authorization gate even if the session token is still valid.
How do you handle stolen-laptop scenarios?
From any other signed-in device, the user can list all active sessions and devices. Log out everywhere is one click. The thief's session dies the moment the operation commits, and the device is moved to the soft-deleted state for audit.
Do you support hardware security keys?
FIDO2 / WebAuthn passkey enrollment is the active build target this quarter. Hardware-key-only enforcement (an org policy that bans TOTP) is on the roadmap immediately after.
What about impossible-travel detection?
We currently surface country-change signals in the security event log as soft warnings. Hard impossible-travel scoring (mph between two known coordinates) is in active development; the hook is a step-up auth challenge, never a silent block.
Where can I see every device + session for my organization?
Org admins with the audit_logs permission see all member sessions and devices across the org, with filters by user, device, IP, and time. Individual users always see their own list at /settings/security.
Is the session activity log immutable?
Yes. Security events recorded via Credo.Security.Event.record/3 — MFA challenges (passed, failed, enrolled), SCIM-driven user lifecycle, and step-up MFA at sealing (blocked, passed, failed) — are mirrored row-for-row into the per-organization hash-chained audit_logs table that also holds the seal events. Tampering with any mirrored record invalidates every record after it on the per-org chain; the chain is independently verifiable via Credo.Enterprise.Audit.AuditChain. See the audit chain page for the mirror semantics and best-effort failure mode.

Trust the device, prove the human, log everything

Three commitments, one consistent surface. Talk to our team about org-specific session policies.