Trust cookie HMAC signing, verification, and mass revocation.
"Trust this browser" allows users to skip MFA on recognized browsers. The trust cookie is an HMAC-signed payload containing the user_id, trust_epoch, and issued timestamp. Verification checks the HMAC signature, user_id match, epoch match, and TTL.
Security Properties
- HMAC-signed via
Plug.Crypto.sign/4using the app'ssecret_key_base(D-46) - Cookie payload:
{user_id, trust_epoch, issued_at}(D-52) - Mass revocation via
mfa_trust_epochincrement on user record (D-48) - HttpOnly, Secure, SameSite=Lax cookie options (D-54)
- Verified against current user_id to prevent cross-user cookie reuse (Pitfall 6)
Summary
Functions
Returns the trust cookie name.
Removed: use cookie_opts/1 with a %Sigra.Config{} so :cookie_domain
is honored.
Returns remember-me cookie options honoring :cookie_domain from the given config.
Revokes all trusted browsers for a user by incrementing mfa_trust_epoch.
Signs a trust cookie for the given user and epoch.
Verifies a trust cookie against the current user and epoch.
Functions
@spec cookie_name() :: String.t()
Returns the trust cookie name.
@spec cookie_opts() :: no_return()
Removed: use cookie_opts/1 with a %Sigra.Config{} so :cookie_domain
is honored.
Previously this arity-0 form returned domain-unaware cookie options as a
backwards-compatibility shim. It was removed because silently dropping
:cookie_domain reopens the subdomain-auth bug Phase 10 fixed: any
caller still using this form would write cookies without the configured
domain and break subdomain sign-in without a single compile error.
Call cookie_opts/1 with your %Sigra.Config{} instead.
@spec cookie_opts(Sigra.Config.t()) :: keyword()
Returns remember-me cookie options honoring :cookie_domain from the given config.
When config.cookie_domain is nil, returns the base host-only options.
When it is a binary (e.g., ".example.com"), appends domain: <value>.
@spec revoke_all(module(), module(), term()) :: {non_neg_integer(), nil}
Revokes all trusted browsers for a user by incrementing mfa_trust_epoch.
Uses update_all for atomic increment. All existing trust cookies
become invalid because their epoch no longer matches.
Parameters
repo- The Ecto repo moduleuser_schema- The generated user Ecto schema moduleuser_id- The user's ID
@spec sign(String.t(), term(), non_neg_integer(), pos_integer()) :: binary()
Signs a trust cookie for the given user and epoch.
Returns a binary cookie value signed with HMAC via Plug.Crypto.sign/4.
Parameters
secret_key_base- The host app's secret key baseuser_id- The authenticated user's IDtrust_epoch- The user's currentmfa_trust_epochvaluetrust_ttl- Trust cookie TTL in seconds
@spec verify(String.t(), binary(), term(), non_neg_integer(), pos_integer()) :: {:ok, term()} | {:error, :invalid}
Verifies a trust cookie against the current user and epoch.
Returns {:ok, user_id} if valid, {:error, :invalid} otherwise.
Checks: HMAC signature, TTL, user_id match, and trust_epoch match.
Parameters
secret_key_base- The host app's secret key basecookie- The trust cookie value to verifycurrent_user_id- The currently authenticated user's IDcurrent_trust_epoch- The user's currentmfa_trust_epochvaluetrust_ttl- Trust cookie TTL in seconds