KemalIdentity::OIDC::Pending
What the application has to remember between sending somebody to the provider and getting them back.
Three secrets and a destination, and each one closes a specific attack:
state— compared on the callback. Without it, an attacker can hand a victim a callback URL carrying the attacker's own authorization code, and the victim's browser silently links the attacker's provider account to the victim's session. That is login CSRF, andstateis the only thing that stops it.nonce— carried into the ID token by the provider and compared here. It binds the token to this authorization request, so one collected elsewhere cannot be replayed into this flow.code_verifier— the PKCE secret. Only its hash goes to the provider, so an attacker who intercepts the authorization code still cannot exchange it.return_to— where to send the person afterwards, validated on the way in rather than on the way out. SeeClient#authorize.
Storing it
This is short-lived, per-flow state. Two places make sense: a table keyed by state, or a
signed, HttpOnly, SameSite=Lax cookie scoped to the callback path. The cookie is what
KemalIdentity::Kemal does — it needs no schema, and it is exactly as trustworthy as the
signing key, which the CSRF layer already depends on.
Wherever it goes, it is a credential: code_verifier is a secret, and this struct
redacts itself for the same reason every other secret-holding type here does.
Constants
How many entries a context may hold.
How long one key may be, in bytes.
How long one value may be, in bytes.
Constructors
Instance methods
The code_challenge sent to the provider: base64url of SHA-256 of the verifier.
S256, never plain. A plain challenge is the verifier, so it protects against
nothing an interceptor of the authorization request could not already do — and a client
that offers both can be downgraded to the weaker one by a provider that accepts it.
The application's own state, carried through the provider and handed back unchanged.
The shard does not know what any of it means. It is here because the integrity of it is this codec's job and nothing else in the flow can do it: an account-linking callback has to know that it is a linking flow, whose account started it and from which session, and an application left to invent that ends up with a second signed cookie beside the one already being signed — or an unsigned one, which is the attack.
client.authorize(
return_to: "/settings/security",
context: {
"flow" => "link",
"account_id" => principal.subject,
"session_id" => principal.session_id.to_s,
},
)
Signed, not sealed — so it is not secret. The browser can read every value, exactly
as it can read the PKCE verifier next to it. Provider tokens, credentials and anything
else that must not be read do not go here; if the state itself has to stay hidden, it
belongs server-side in a table keyed by state. ASP.NET Core can put an account id in
its equivalent because it encrypts it; this one only proves nobody changed it.
Compare it on the callback. Carrying session_id does nothing on its own — the
application has to check it against the session presenting the callback, which is how
Keycloak binds its own account-linking flows. The shard deliberately does not do that
comparison: which account a federated identity may be attached to is the one decision
docs/02-security-model.md says this shard must never make on an application's behalf.
Bounded, because the carrier is a cookie: PendingCodec refuses to seal a flow larger
than 4 KiB, and an oversized one would otherwise be written by the browser and silently
fail to open on the way back — a flow nobody can complete and nothing reports.
Whether this flow started too long ago to still be completed.
A login that has been sitting in a tab for a day is not a login in progress. Expiring it
bounds how long a captured state cookie is worth anything.
Appends this struct's name and instance variables names and values to the given IO.
struct Point
def initialize(@x : Int32, @y : Int32)
end
end
p1 = Point.new 1, 2
p1.to_s # "Point(@x=1, @y=2)"
p1.inspect # "Point(@x=1, @y=2)"