KemalIdentity::OIDC::Client
Drives an Authorization Code + PKCE flow against one Provider.
Two calls, and the application carries Pending between them:
request = client.authorize(return_to: "/dashboard")
# store request.pending, then redirect to request.url
identity = client.complete(pending, state: ..., code: ...)
#complete returns an Identity or a Failed, never a raised exception for anything the
browser or the provider controls — the callback is a public endpoint reachable by anybody
with a URL.
Constants
How long a flow may sit unfinished.
A login that has been open in a tab since yesterday is not a login in progress, and the
window is how long a captured state is worth anything.
How long to wait on the token endpoint. Same reasoning as JWKS::DEFAULT_TIMEOUT: a
provider that accepts connections and never answers must not hold a fiber indefinitely.
Largest token response accepted, before it is parsed.
Constructors
Class methods
Whether value is somewhere this application may send a browser after login.
Only a same-site absolute path. The cases that matter and are easy to miss:
//evil.example.com— a browser reads a leading//as protocol-relative, so this is an absolute URL wearing a path's clothes. It is the classic open redirect./\evil.example.com— some browsers normalise the backslash to a slash, producing the same thing.https://evil.example.com— an absolute URL, plainly.- A newline or a control character — header splitting, if the value ever reaches a
Locationunescaped.
Instance methods
Starts a flow: where to send the browser, and what to remember.
return_to is validated here, on the way in, rather than on the callback. That is
deliberate: by the callback the value has made a round trip through the provider and back
through the browser, and anything checked only then is checked on attacker-influenced
input. Checked now and carried in signed state, it cannot be substituted.
Only a same-site path is accepted — /dashboard, not https://elsewhere.example.com and
not //elsewhere.example.com, which a browser reads as a protocol-relative absolute
URL and is the open redirect people miss. Anything else is dropped rather than raising:
a hostile ?return_to= in a link is a thing to ignore, not a 500.
context is the application's own state, carried in the signed pending flow and handed
back on the callback — what kind of flow this is, which account started it, which session.
Pending#context says what may go in it, and what may not: it is signed rather than
encrypted, so it is not a place for secrets, and comparing it on the way back is the
application's job.
Finishes a flow from the callback's parameters.
The order below is the order it has to be in. state is compared before anything is sent
anywhere, because a mismatched state means this callback did not come from a flow this
application started, and exchanging its code would be doing an attacker's work.