struct

KemalIdentity::OIDC::Provider

Inherits Struct < Value < Object

One OpenID Connect provider, as a client of it.

docs/00-scope.md puts being an authorization server permanently outside this shard. This is the other side: the application redirects somebody to Google or Okta or an internal provider, and gets back an assertion about who they are.

Authorization Code with PKCE, and nothing else

No implicit flow, no hybrid, no resource-owner password grant. The implicit flow puts a token in a URL fragment, where it lands in browser history and in any Referer that leaks; the password grant asks your application to handle somebody else's password, which is the thing federating was supposed to avoid. Both are discouraged by the OAuth 2.1 draft and neither is expressible here.

PKCE is not optional either, including for a confidential client with a secret. It costs a hash and it closes authorization-code interception, which a client secret does not: the secret proves the client, and PKCE proves the request came from the same place the code was issued to.

Constants

RESERVED = ["response_type", "client_id", "redirect_uri", "scope", "state", "nonce", "code_challenge", "code_challenge_method", "prompt"] of ::String

Everything Client#authorize puts in the query string itself.

Constructors

new(issuer : String, client_id : String, authorization_endpoint : String | URI, token_endpoint : String | URI, redirect_uri : String, keys : JWT::KeySource, client_secret : Secret | Nil = nil, scopes : Array(String) = ["openid", "email", "profile"], algorithms : Array(String) = ["RS256"], authorization_params : Hash(String, String) | Nil = nil)
Source

Instance methods

algorithms
Source
authorization_endpoint
Source
authorization_params

Extra query parameters to add to the authorization request, for the things a provider wants and no standard names: Google's hd, Okta's login_hint, Azure's domain_hint.

Allowlisted by exclusion, and refused at boot

RESERVED below is every parameter Client#authorize builds itself, and a key matching one of them raises ConfigurationError at construction rather than being silently dropped or silently winning. Four of them — state, nonce, code_challenge, code_challenge_method — are the flow's security, and an application that could overwrite them could turn PKCE off by configuration. The rest name the client and where the code comes back to.

prompt is reserved too, and is not a security value: Client#authorize(prompt:) sets it, and two prompt parameters in one query string is a request the provider gets to interpret however it likes.

Values are escaped by URI::Params, so nothing here can inject a separator.

Source
client_id
Source
client_secret

The client secret, for a confidential client. nil for a public one.

A public client — a CLI, a desktop application — has nowhere to keep a secret, and PKCE is what protects it. A server-side web application is confidential and should have one.

Source
confidential?
Source
inspect(io : IO) : Nil

Redacted: a config dump in a crash report must not print a client secret.

Source
issuer

The iss every ID token from this provider must carry, compared exactly.

Not a base URL to build others from and not a display name — it is the identity of the party whose assertions you are about to believe, and half of the (issuer, subject) pair that names an external account forever.

Source
keys

Where the provider's signing keys come from. Normally a JWT::JWKS over its jwks_uri, so a rotation is picked up without a restart.

Source
redirect_uri

Where the provider sends the browser back.

Registered with the provider and compared by it exactly, character for character — which is the check that stops an attacker registering a lookalike path and collecting codes. It is sent again at the token exchange for the same reason.

Source
scopes
Source
to_s(io : IO) : Nil

Redacted: a config dump in a crash report must not print a client secret.

Source
token_endpoint
Source