Let’s go over some OIDC flows, what makes them different, and when to use each one.
![]()
Basics
Ever visited a website which asked you to log in with Google, Facebook, or another identity provider? That’s OpenID Connect (OIDC) in action. It leverages another entity to handle authentication. Which means you don’t have to deal with all the security, GDPR compliance and other headaches.
But how does it work under the hood?
To understand OIDC on a basic level, we can break a .well-known/openid-configuration document together, which describes the endpoints and capabilities of an OIDC provider.
Google’s .well-known/openid-configuration
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"revocation_endpoint": "https://oauth2.googleapis.com/revoke",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"email",
"profile"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"iss",
"name",
"picture",
"sub"
],
"code_challenge_methods_supported": [
"plain",
"S256"
],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code",
"urn:ietf:params:oauth:grant-type:jwt-bearer"
]
}
Authorization Code Flow
The most common flow, used for server-side applications that are capable of secure storage of client secrets.
Parameters additional to the .well-known/openid-configuration:
Client IDnot sensitive, identifies the client application.Client Secretsecretly shared to the client application. Should be kept confidential at all times.Redirect URIwhere the authorization server will send the user after authentication. will have to be whitelisted in the OIDC provider.
-
User accesses a client application, which requires authentication.
-
If the app is properly written it will already have cached and parsed the
.well-known/openid-configurationdocument. -
The app redirects the user to the
authorization_endpointwith the following parameters:
Example:https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=CLIENT_ID&redirect_uri=https://yourapp.com/callback&scope=openid -
After authentication and consent from user, the authorization server redirects back to the
redirect_uriwith an authorization code.
Example:https://yourapp.com/callback?code=AUTHORIZATION_CODE -
Now the sensitive part happens, the client application exchanges the authorization code for an access token and ID token by making a POST request to the
token_endpointwith the following parameters:grant_type=authorization_codecode=AUTHORIZATION_CODEredirect_uri=https://yourapp.com/callbackclient_id=CLIENT_IDclient_secret=CLIENT_SECRET
Obviously, this request should always happen server-side, never in the browser.client_secretshould never be exposed to the client.
-
The authorization server responds with an access token and ID token.
Example response:{ "access_token": "ACCESS_TOKEN", "id_token": "ID_TOKEN_JWT", "expires_in": 3600, "token_type": "Bearer" }The application should now verify the signature of
ID_TOKEN_JWTusing the public keys from thejwks_uriendpoint, and then decode it to get user information.
If we decode theID_TOKEN_JWT, we can see the user information:{ "iss": "https://accounts.google.com", "sub": "USER_ID", "email": "USER_EMAIL", "exp": 1712345678, "iat": 1712342078 }
What if an application is not capable of securely storing the client secret?
In the case of SPA apps, mobile etc. The application should use the PKCE (Proof Key for Code Exchange) extension to OIDC. PKCE allows public clients to securely authenticate without needing a client secret.
The flow is similar to the Authorization Code Flow, but with the subtraction of client_secret and addition of a code_challenge and code_verifier.
- The client generates a
code_verifier, which is a random string each time. - The client then creates a
code_challengeby hashing thecode_verifierusing SHA-256. - The client sends the
code_challengealong with the authorization request to the authorization server.
/o/oauth2/v2/auth?response_type=code&client_id=CLIENT_ID&redirect_uri=https://yourapp.com/callback&scope=openid&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
(make sure this is supported by the OIDC provider, which is the case for Google). - After the user authenticates and the authorization server redirects back to the
redirect_uriwith an authorization code, the client sends a POST request to thetoken_endpointwith the following parameters:grant_type=authorization_codecode=AUTHORIZATION_CODEredirect_uri=https://yourapp.com/callbackclient_id=CLIENT_IDcode_verifier=CODE_VERIFIER
- This is the crucial part: the authorization server verifies that the
code_verifiermatches thecode_challengesent in the initial request. If they match, it responds with an access token and ID token. This ensures that the request is coming from the same client that initiated the flow, preventing interception attacks.
Try to always stay OIDC conformant
OIDC is a standard, and while it allows for some flexibility, it’s best to stick to the standard flows and parameters. This ensures compatibility with various OIDC providers and libraries. If you find yourself needing to deviate from the standard, consider whether it’s worth rewriting the whole auth the next time another OIDC provider is knocking on the door. 😉