KemalIdentity::Principal
Who is making this request, since when, and at what assurance.
The minimum security context and nothing more. It carries no roles, no permissions, no
email, no profile — those are authorization or application concerns, they go stale the
moment they are copied, and loading them would put a join on the hot path
(docs/03-data-model.md). The application loads its own user object when it actually
needs one.
#credential is not an exception to that rule. A role goes stale — somebody is removed
from a team and the copy in a session says otherwise. "This request was proved by token
tok_reporting" cannot go stale: it is a fact about this request, established at the same
instant as assurance and authenticated_at, and it is false about no request it was
attached to.
Constructors
Instance methods
When the credential behind assurance was last verified.
Not a stored fresh_until: the freshness window belongs to the caller
(require_fresh!(within: 5.minutes)), so storing a precomputed deadline would bake
one caller's policy into every row.
The credential that proved this request, when one was presented.
This used to be session_id : String? and did the same job for exactly one credential
kind, which is why #session_id below still answers. Generalising it is what lets an
application tell one of its own personal access tokens from another — see
blueprints/0021-credential-reference.md.
nil for a principal that no credential produced: the result of verifying a password at
login, before Sessions::Service#start mints anything, and any principal an application
constructs itself. nil reads as unattenuated, so it changes no decision on its own.
Whether the credential behind this principal was verified within within of now.
A principal below AssuranceLevel::Password is never fresh, however recent it is: a
session restored from a remember-me cookie proves possession of a stored token, not
the presence of the account holder. Step-up therefore always forces a real
re-authentication out of Remembered.
Whether the password behind this principal was typed within within of now.
False when no password was ever typed for it, which is the whole point: "they were authenticated recently" and "they typed their password recently" are different questions, and only the second one may gate linking a new credential to the account.
When the password behind this principal was last actually typed, if it ever was.
authenticated_at cannot stand in for this. It is restamped by every assurance increase,
so proving a second factor nine minutes after logging in satisfies a require_fresh!
window that meant "type your password again". Nor can assurance: Password says one
factor was proved, not which one, and a federated login sits at that level too.
nil is the answer for every principal no password produced — a remembered browser, a
bearer token, a federated login, a session adopted from a legacy system — and for an
account that has no password at all. #password_verified? therefore answers false for
them rather than raising or guessing, which is the fail-closed direction: an application
that never records a password gets a guard that always refuses, not one that always
passes.
The session this principal was resolved from, when there is one.
Derived from #credential rather than stored beside it: one idea, one home. Present so
that "log out everywhere else" can spare the current session, and so that CSRF can anchor
on the session that is actually signed in.
A bearer credential answers nil here even though it has an id of its own, which is the
point — there is no session to spare or to anchor on.
The canonical account identifier, as a string.
A String rather than a generic parameter on purpose: RequestAuthenticator(T)
would propagate T through every handler, service and repository in the type graph,
and an application wanting a UUID in one place and an Int64 in another would have
no way out. The cost is one subject.to_i64 at the application boundary — see
docs/01-architecture.md.
The tenant this principal is confined to, or nil for one that is not confined.
Read by Authz::RBAC#decide, which refuses a principal bound to one tenant asking about
another before it consults membership at all — so this is an authorization input, not a
label. Nil is unconstrained, which is the single-tenant deployment and also the person
who belongs to several organisations.
For a session principal this is a copy of the account's tenant taken when the session was
minted; Sessions::Record#tenant_id says what that means for a change made afterwards.