OpenID Connect

·

OpenID Connect (OIDC) is a specification built on top of OAuth 2.0 that allows applications to authenticate users via third-party Identity Providers.

Identity Providers can be certified if their service conforms to the OpenID Connect specification.

Authentication Flow

There are two types of authentication flow: "code", where communication with the Identity Provider happens via a server, and "implicit", where the client (e.g. a single-page application running in a web browser) communicates directly with the Identity Provider. Only the "implicit" flow is discussed here.

At the start of the implicit authentication flow, the client sends the user to an authorization endpoint, e.g. https://accounts.google.com/o/oauth2/v2/auth. If it's not already known, that endpoint can be found via a discovery document, e.g. https://accounts.google.com/.well-known/openid-configuration, and there's even a way to discover that discovery document via webfinger, but that part of the process isn't essential here.

The request includes some query parameters:

const params = new URLSearchParams()
params.set('scope', 'openid') // use the OIDC protocol
params.set('client_id', CLIENT_ID) // the client identifier
params.set('redirect_uri', REDIRECT_URI) // redirect to this URL 
params.set('response_type', 'id_token') // return the id_token in a URL fragment
params.set('nonce', randomString()) // a random string to be included in the id_token
params.set('prompt', 'none') // don't prompt unless necessary

location.href = AUTHORIZATION_ENDPOINT + '?' + params.toString()

The Identity Provider must verify that this client_id and redirect_uri pair have previously been registered, so the id_token will only be sent to the application which the user has approved.

If the user declines, they're sent back to the redirect_uri with a URL fragment containing an error.

If the user approves, they're sent back to the redirect_uri with a URL fragment containing an id_token, which the client then stores. The client must check that the nonce value in the id_token matches the nonce value that was sent in the original request, so that an id_token can't be forced upon the user unless it was requested.

The client then includes the id_token in the HTTP headers of requests to an API that requires authentication (the Relying Party), as Authorization: Bearer ${id_token}.

JSON Web Token

The id_token is a JSON Web Token (JWT), which is signed but not encrypted, so you can read the contents.

The id_token has 3 parts, separated by a . character:

  • The first part of the token contains the base64url-encoded header, which describes the algorithm and key used to create the signature for the payload.

  • The second part of the id_token contains the base64url-encoded payload.

  • The third part of the id_token contains the signature, which confirms that the payload of the id_token hasn't been modified.

    The "RS256" signature algorithm is asymmetric: a private key is used to sign the token, and a public key is used to verify the signature. The public key can be retrieved from a JSON Web Key Set (JWKS) endpoint, e.g. https://www.googleapis.com/oauth2/v3/certs (this URL can be found via the same discovery document as the authorization endpoint). The key set contains a list of keys - to validate the signature take the key with the kid value that matches the kid value in the header of the id_token. Once a key has been fetched, it should be cached locally as it won't change (when a new key is used for signing the id_token it will have a different kid).

The payload

The payload contains several important values, which the server must verify:

  • exp: the expiry date of this token, after which the token is no longer valid.
  • aud: the "audience" for this token, which must match the client_id used to request it (this prevents a site that receives an id_token from a user using it to impersonate the user on another site).
  • iss: the "issuer" of this token, which must be an expected value (e.g. "https://accounts.google.com").

The unique identifier of the user within the issuer's domain (i.e. their account ID) will be included in the id_token as the sub value.

If the client has asked for other information to be included, e.g. by adding email or profile to the scope parameter in the original request, those values will also be included in the id_token.

Expiry

When the token expires, the client can get a new token using "silent authentication": open the same authorization endpoint as before, but in a hidden iframe. If the user's approval is still valid (their session with the Identity Provider will be stored in a cookie which the authorization endpoint in the iframe, being on the Identity Provider's domain, can read), the iframe will redirect back to the redirect_uri for the new id_token to be read from the URL fragment and stored as before.

Further reading