OAuth / OIDC (OpenID Connect)¶
OAuth is an authentication framework that involes lots of standards and supports many different use cases.
OIDC is based on OAuth 2.0 for more specific use cases.
As far as I can tell there are two main use cases:
- login at a website (SP / OIDC Client) using a centralized account via an IdP (OP)
- authorize a “native application” (email client, cloud-sync daemon, CLI applications, …) to access a certain resource (“scope”) owned by your account
An IdP is identified by its “issuer URL”; the metadata (JSON) for clients (“how to use the IdP”) should be located at ${issuer}/.well-known/openid-configuration.
The are different login flows:
- Authorization Code Flow (using response type
code) - Implicit Flow (using response type
id_tokenorid_token token) - Hybrid Flow (using any response type that include
codeand at least one ofid_tokenortoken)
Only Authorization Code Flow should be used.
Service (RP) specific applications might use OAuth as well - say a synchronization of local files to a cloud service; in those cases the service (as “Identity Provider”) should have specific documentation how to use the API, including authentication and OAuth (and perhaps how to request tokens that only have restricted access). This document is not about such uses. The service may also use OIDC to authenticate users with an OP, which IS what this document is about.
External links¶
- RFC 6749 The OAuth 2.0 Authorization Framework
- https://openid.net/wg/connect/specifications/
- PKCE: RFC 7636 Proof Key for Code Exchange by OAuth Public Clients
- PAR: RFC 9126 OAuth 2.0 Pushed Authorization Requests
- RFC 9700 (BCP 240): Best Current Practice for OAuth 2.0 Security
- IANA Registry: OAuth Parameters
Clients¶
A client is identified by a client_id; a client uses this client_id and a client_secret (empty for “Public Clients”, “Confidential Clients” require a proper secret) to authenticate requests with OP endpoints.
In the OIDC Context each SP is a client.
Authentication can be done via (see https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#token-endpoint-auth-method):
client_secret_basic-
Using
client_idandclient_secretto build an HTTPAuthorization: Basic ...header. client_secret_post-
Passing
client_idandclient_secret(might be omitted if empty) as additional parameters to POST data.Prefer using
client_secret_basicinstead. client_secret_jwt,private_key_jwt-
See https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
A client registration usually should include:
client_idandclient_secret(both usually assigned by the OP)redirect_uris: allowedredirect_urivalues forauthorization_endpoint- if client-initiated logout with redirect is desired: list of allowed
post_logout_redirect_uris - the client should be given the OP issuer URL
- clients might be unable/unwilling to read metadata from the OP based on the issuer URL; you might have to copy various fields to the client configuration manually
- OP should/might impose additional restrictions:
- allowed scopes
- allowed
response_types in login flow (allow onlycodefor Authorization Code Flow) - allowed
grant_types in token endpoint:authorization_coderequired for Authorization Code Flow,refresh_tokenif desired token_endpointauth methods:client_secret_basic
- optionally a service description / homepage URL
- optionally register JWKs (JSON Web Keys) to encrypt JWTs from OP to client (or sign JWTs from client to OP)
An OP might support dynamic registration of clients: (RFC 7591 OAuth 2.0 Dynamic Client Registration Protocol)[https://datatracker.ietf.org/doc/html/rfc7591].
Note
I recommend not allowing dynamic registration; use either public clients or require explicit registration through a known contact (e.g. registered user on your platform).
Tokens and codes¶
authorization_code-
Actually only named
codein protocol fields. Can be used (once) to retrieveid_tokenandaccess_token(and possibly arefresh_token) with thetoken_endpoint.Requested by response type
code; usually expires quickly (e.g. after 10-60 seconds). access_token-
HTTP “bearer” code that can be used to “act” on behalf of the user with target service (e.g. accessing the
userinfo_endpoint).Access is usually limited to certain (requested and granted)
scopes and the client the token is bound to.Requested by response type
token. id_token-
Contains basic user information (“claims”) as signed JWT.
Requested by response type
id_tokenor returned bytoken_endpoint.When the token is received from
token_endpointthe signature isn’t relevant (doesn’t need to be verified). Only when passed through a response type including “id_token” the signature must be verified using the keys located atjwks_urifrom the metadata. refresh_token-
Can be used to refresh
access_tokenandrefresh_tokenwith thetoken_endpoint.
Claims¶
Claims are user attributes and might be returned either in the id_token or from the userinfo_endpoint.
Also see https://openid.net/specs/openid-connect-core-1_0.html#Claims and https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims.
Important claims:
iss-
OP issuer URL.
Unclear whether present in userinfo or only
id_token. sub-
long-term stable identifier for user. A user is uniquely identified by the OP issuer and this field.
Often randomly generated (and stored) by the OP for a specific client (for privacy reasons), but can also use the same value across multiple clients.
Always present (or might require scope
openid, which is required anyway). name-
Full name of user for presentation (scope
profile). preferred_username-
Preferred account name; not necessarily unique, but can be if enforced by OP (scope
profile). email-
E-mail address (scope
email). email_verified-
Whether OP asserts the
emailaddress is valid (scopeemail). sid:-
Session identifier for use with Front-/Back-Channel Logouts
Endpoints at OP¶
Also see metadata overview: https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#authorization-server-metadata.
authorization_endpoint¶
Browser endpoint to start login flow with (using a normal redirect / “link open” -> HTTP GET).
Query Parameters are:
response_type: should passcodeto use Authorization Code Flow (supported values:response_types_supportedin metadata)scope: space-separated list of scopes, should include at leastopenid(supported scopes:scopes_supportedin metadata)client_idredirect_uri: where to go (redirect -> HTTPGET) once login finishedcode:authorization_codefrom the login (as requested by usingcodeinresponse_type)- optional
state(as passed toauthorization_endpoint) - optional
isswith the issuer URL (when OP supports RFC 9207 OAuth 2.0 Authorization Server Issuer Identification)
- optional
state: forwarded toredirect_uri, should be used to bind login flow to session at client - optional
nonce: gets included in ID tokens created from this flow (another way to bind login flow to session) - optional PCKE (should be used with public clients, not needed with proper
client_secrets):code_challenge: (SHA256) hash of a randomcode_verifiercode_challenge_method: should useS256(supported methods:code_challenge_methods_supportedin metadata)- server associates
code_challengeandcode_challenge_methodwithauthorization_codecreated from this flow - client remembers
code_verifierto use withtoken_endpointand returnedauthorization_code
Instead of sending all those parameters via the browser redirection a client can use PAR (if supported by the OP); see pushed_authorization_request_endpoint below; then the query parameters are only:
client_idrequest_urias returned by PAR
pushed_authorization_request_endpoint (PAR)¶
See PAR: RFC 9126 OAuth 2.0 Pushed Authorization Requests.
Instead of passing parameters to the authorization_endpoint a login flow can be started using this endpoint; parameters are the same with the following exceptions:
request_urinot allowed- needs client authentication, e.g. by passing
client_secret
Todo
unclear if client_secret_basic should work too, not just the client_secret_post variant
As redirect_uri is passed through a trusted channel the OP might allow more or simply any values instead of checking against the set of allowed redirect_uris.
end_session_endpoint¶
Browser endpoint to trigger logout flow; see https://openid.net/specs/openid-connect-rpinitiated-1_0.html.
Note
client usually should pass id_token of user to logout as id_token_hint; only this client session (if verified!) should be automatically closed; even then the OP might ask for confirmation first. (An OP might trust certain clients to trigger a full logout.)
No GET-request / cross-site request should logout a user without confirmation by the user on the OP site.
The OP might ask whether other client sessions should be closed too and whether a full logout should be triggered.
token_endpoint¶
API endpoint for clients (HTTP basic authentication with $client_id:$client_secret) to:
- convert
authorization_codetoaccess_token,id_tokenandrefresh_token:POSTform-data with:grant_type:authorization_codecode:authorization_codefrom login flowredirect_uri: theredirect_uriused in the request toauthorization_endpoint- when PCKE was used:
code_verifier
- returning
refresh_tokenprobably depends on a certain scope - see https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
- use
refresh_tokento refreshaccess_tokenandrefresh_token- similar to
authorization_code, but:grant_type:refresh_tokenrefresh_tokenwith previous value instead ofcode- no
id_tokenin response
- see https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens
- similar to
For public clients (no/empty client_secret) the request is sent without HTTP authorization, and client_id is passed as additional parameter instead (RFC 6749 §2.3.1. Client Password).
Other grant types (grant_types_supported in metadata):
client_credentials-
Privileged client requesting access tokens, see RFC 6749 §4.4. Client Credentials Grant.
password-
Get
access_tokenby sending username and password,see RFC 6749 §4.3. Resource Owner Password Credentials Grant. urn:ietf:params:oauth:grant-type:device_code-
See Device Authorization below.
urn:openid:params:grant-type:ciba-
See CIBA below.
userinfo_endpoint¶
API endpoint for clients to get more data (“claims”) for an access_token; uses access_token as bearer authentication.
The id_token might not contain all claims a client is interested in (especially when limited in size due to being passed through URL query parameters); this endpoint returns all data (“claims”) the client is allowed to see.
pushed_authorization_request_endpoint¶
registration_endpoint¶
For use with (RFC 7591 OAuth 2.0 Dynamic Client Registration Protocol)[https://datatracker.ietf.org/doc/html/rfc7591].
revocation_endpoint¶
A client can revoke access_tokens and refresh_tokens using the endpoint; see RFC 7009 OAuth 2.0 Token Revocation.
introspection_endpoint¶
Tokens (access_tokens and refresh_tokens) can be inspected using this endpoint; usually a client authentication should be required (and only tokens associated with client should be usable).
See RFC 7662 OAuth 2.0 Token Introspection.
device_authorization_endpoint¶
See Device Authorization below.
backchannel_authentication_endpoint¶
See CIBA below.
Session concepts¶
There aren’t many descriptions on how sessions are handled.
access_tokens should have a fixed (and short, “use once” style) lifetime, but an explicit logout or revokation must invalidate them - i.e.userinfo_endpointwon’t work anymore andintrospection_endpointshould returnactive=False.refresh_tokens have a longer lifetime; again an explicit logout or revokation must invalidate them.- with scope
offline_accesstokens should outlive the normal OP browser session (see https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess). - when the OP browser session ends tokens should be invalidated too (expect for the
refresh_tokens)- this depends on the OP implementation though
- explicit logouts should trigger Front- and Back-Channel Logouts
- probably should use the
sidclaim, at least for Back-Channel Logouts targetting specific sessions or Front-Channel Logouts.
- probably should use the
- password resets (or accounts getting disabled) should trigger logouts; removing other credentials (MFA tokens) should trigger logouts of related sessions (or simply all sessions)
Front-Channel Logout¶
See https://openid.net/specs/openid-connect-frontchannel-1_0.html.
Front-Channel uses the browser to tell the client to end the session; this can be used when the OP can’t talk to the client directly (e.g. due to firewall reasons), but the browser can.
Note
I’d avoid this method completely. If an client implements it, it really should require the iss and sid parameters and only end the matching session.
Back-Channel Logout¶
See https://openid.net/specs/openid-connect-backchannel-1_0.html.
This way an OP can end client sessions. It involves a JWT-signed “Logout Token”, which the client must verify using keys located at jwks_uri in the metadata.
Logins without redirect from browser¶
A client without a browser (or perhaps running on a device where you wouldn’t want to trigger a normal login and ask for the password) can try other forms of authentication; they’ll require a second device to complete the authentication.
Device Authorization¶
See RFC 8628 OAuth 2.0 Device Authorization Grant.
The client uses the device_authorization_endpoint from the metadata to start authentication.
The response includes verification_uri and user_code that should be displayed to the user (to complete the authentication on some other device).
As an alternative the response might include a verification_uri_complete (combining verification_uri and user_code) to be communicated in a machine-readable way (e.g. a QR code).
The response also includes a device_code that the client uses to poll the token_endpoint combined with grant_type=urn:ietf:params:oauth:grant-type:device_code.
CIBA: Client-Initiated Backchannel Authentication Flow¶
See https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html
The client uses the backchannel_authentication_endpoint from the metadata to start authentication; it should include a binding_message, which both client and OP display.
The response for backchannel_authentication_endpoint includes an auth_req_id that can be used with grant_type=urn:openid:params:grant-type:ciba at the token_endpoint (unless CIBA is used in “Push Mode”).
In “Ping Mode” or “Push Mode” the OP notifies the client when the authentication is done, in “Poll Mode” the client polls.
Note
The OP should reject duplicated (even similar) binding_messages; the user must be able to identify mismatches in case of an attack (e.g. by someone watching the screen).
Note
It seems the user will need to find the OP “dashboard” to grant the authentication request some other way (maybe from an email notification, …).