Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Designing OpenID Connect (OIDC) on OAuth 2.0

Hello! Let's continue our journey into designing identity platforms.

In our last lesson, we designed a comprehensive system for managing scopes and user consent within an OAuth 2.0 framework. We established that OAuth is an authorization protocol, designed to grant applications limited access to resources. This naturally leads to a crucial question: how do we handle authentication—securely identifying who the user is in the first place?

This lesson addresses exactly that by focusing on our next learning outcome: Design the OIDC layer on top of OAuth 2.0, including ID token generation, claims management, and the UserInfo endpoint.

We will architect the components that transform our OAuth 2.0 authorization server into a full-fledged Identity Provider (IdP). Specifically, we will cover:

  1. The "Why" of OpenID Connect (OIDC): Understand the problem OIDC was created to solve.
  2. The ID Token: Design this core artifact, which is a JWT that proves a user's identity.
  3. Claims Management: Architect how clients request user information (claims) and how the server delivers it.
  4. The UserInfo Endpoint: Design a standard API for retrieving additional user attributes.
  5. Key Distribution with JWKS: Design the critical mechanism for securely validating ID tokens in a distributed system.

By the end of this lesson, you will understand how to build a modern, secure, and interoperable identity layer that enables federated login.

1. From Authorization to Authentication: The Need for OIDC

OAuth 2.0 became incredibly popular for delegated authorization. So popular, in fact, that developers started using it for something it was never designed for: authentication. They would treat the possession of a valid access token as proof of a user's identity. This led to a variety of custom, non-standard, and often insecure implementations.

The following video explains this history and why a formal extension was needed.

OAuth 2.0 and OpenID Connect (in plain English)

The video 'OAuth 2.0 and OpenID Connect (in plain English)' by OktaDev clearly explains how OAuth 2.0 was being misused for authentication and how OpenID Connect emerged to standardize this process.

Please watch these two segments: The Problem with OAuth for Authentication (45:23 - 48:57): This part covers the 'hacky' solutions that arose from using OAuth for login. Introducing OpenID Connect (48:57 - 50:35): This explains how OIDC adds a thin, standardized layer on top of OAuth 2.0 to formally handle authentication.

As the video highlighted, OpenID Connect (OIDC) isn't a replacement for OAuth 2.0. It's a thin identity layer built on top of it. It formalizes the process of authentication by adding a few key components to the standard OAuth 2.0 flow:

  • A special scope: openid
  • A new token: the ID Token
  • A standard endpoint for user attributes: the UserInfo Endpoint

2. Architecting the OIDC Flow

To understand how OIDC works, it's critical to distinguish between the two types of tokens involved. The roles of the access token and the ID token are fundamentally different.

A Deep Dive into the Past, Present & Future of OAuth • Aaron Parecki • GOTO 2024

In 'A Deep Dive into the Past, Present & Future of OAuth', Aaron Parecki provides a perfect analogy for understanding the difference between access tokens and ID tokens.

Watch the segment from 13:32 to 16:18. Pay close attention to the distinction he makes: 'Access tokens are for the API, ID tokens are for the app'.

This distinction is the cornerstone of OIDC architecture:

  • Access Token: An opaque string meant for a Resource Server (an API). It represents an authorization to access data. The client application should not try to read or interpret it.
  • ID Token: A structured JWT meant for the Client Application (the "Relying Party"). It represents an authentication event and contains information about the user.

Let's see how these pieces fit together in the full OIDC flow.

OpenID Connect Authentication Flow Sequence Diagram
This sequence diagram shows the OIDC Authorization Code Flow. Notice how the client receives both an ID Token and an Access Token, and uses them for different purposes: the ID Token to identify the user, and the Access Token to call the UserInfo endpoint.

The flow, from an architectural standpoint, is an extension of the OAuth 2.0 Authorization Code flow we're already familiar with:

  1. The client initiates the flow by redirecting the user to the authorization endpoint, but this time it must include openid in the scope parameter (e.g., scope=openid profile email).
  2. The user authenticates with the Identity Provider (our server) and consents to the requested scopes.
  3. The server redirects the user back to the client with an authorization_code.
  4. The client's backend exchanges the authorization_code at the /token endpoint.
  5. This is the key OIDC step: Because openid was in the original scope, the /token endpoint returns not only an access_token but also an id_token.
  6. The client validates the id_token to authenticate the user and establish a session.
  7. The client can later use the access_token to request more user information from the /userinfo endpoint.

3. Deep Dive: Designing the ID Token

The ID Token is a JWT containing claims about the authentication event and the user. As an architect, you need to ensure it's generated according to the OIDC specification.

OpenID Connect explained

The article 'OpenID Connect explained' by Connect2id provides an excellent breakdown of the ID Token's structure and its standard claims.

Please read section 3, 'The ID token'. Focus on the list of claims and the example JSON payload. This details the data contract your authorization server must fulfill when generating an ID token.

Based on the specification, your ID token generation service must populate these key claims:

  • iss (Issuer): The URL of your authorization server. This is a security measure so the client can verify the token came from the expected source.
  • sub (Subject): A unique, stable identifier for the end-user at your server. This should never be recycled.
  • aud (Audience): The client_id of the client application the token is intended for. The client must verify this claim to prevent a token issued for one client from being used at another.
  • exp (Expiration Time): The time after which the ID token MUST NOT be accepted.
  • iat (Issued At Time): The time at which the JWT was issued.
  • auth_time: The time when the end-user authentication occurred.
  • nonce: A string value used to mitigate replay attacks. The client generates a nonce, includes it in the initial authentication request, and your server must include the same nonce in the ID token. The client then verifies it matches.

ID Token Validation and Key Distribution with JWKS

An ID token is useless if the client can't trust it. The client must validate the JWT's signature. This raises a critical architectural question: how does the client get the public key to verify the signature?

The solution is a JSON Web Key Set (JWKS). This is a standardized format for publishing your public keys at a well-known URL.

JWT Issuance and Verification Flow
This diagram illustrates the role of the JWKS endpoint. The Auth Service signs the JWT with a private key, and API Services fetch the corresponding public key from the JWKS endpoint to verify the signature.

Here is how you would design this subsystem:

  1. Generate a Key Pair and kid: Your signing service uses a private key (e.g., RSA or EC) to sign ID tokens. You assign this key a unique Key ID (kid). This kid is a simple string, like "prod-signing-key-2023-10-26".

  2. Include kid in the JWT Header: When signing an ID token, you must include the kid in the JWT's header.

    {
      "alg": "RS256",
      "typ": "JWT",
      "kid": "prod-signing-key-2023-10-26"
    }
    
  3. Publish a JWKS Endpoint: You create a public, unauthenticated endpoint at a standard location: /.well-known/jwks.json. This endpoint returns a JSON object containing an array of your public keys.

    {
      "keys": [
        {
          "kty": "RSA",
          "use": "sig",
          "kid": "prod-signing-key-2023-10-26",
          "alg": "RS256",
          "n": "0vx7agoebG...",
          "e": "AQAB"
        },
        {
          "kty": "RSA",
          "use": "sig",
          "kid": "old-signing-key-2022-01-15",
          "alg": "RS256",
          "n": "...",
          "e": "AQAB"
        }
      ]
    }
    

    This design elegantly supports zero-downtime key rotation: you can add a new key to the JWKS, start signing with it, and clients will automatically pick it up while still being able to validate tokens signed with the old key.

  4. Client-side Validation: When a client receives an ID token, it performs the following steps:
    a. Parses the JWT to read the kid from the header (without verifying the signature yet).
    b. Fetches your /.well-known/jwks.json endpoint (and caches the result for a short period, e.g., 15 minutes).
    c. Finds the key in the keys array with the matching kid.
    d. Uses that public key to verify the JWT's signature.

JWKS and Zero-Downtime Key Rotation

The article 'JWKS and Zero-Downtime Key Rotation' provides a fantastic, engineering-focused guide to the design and implementation of a JWKS endpoint.

This is a great practical guide. Please read the following sections to understand the architectural design: What is JWKS?: The basic definition. The Problem JWKS Solves: The rationale for using this pattern over hard-coding keys. The Key ID (kid): Why It's Critical: Explains the role of the kid header. The Standard Location For JKWS: Explains the /.well-known/jwks.json convention. How JWKS Enables Zero-Downtime Key Rotation: Walks through the operational steps for a smooth key rotation.

4. Claims Management and the UserInfo Endpoint

Beyond the standard authentication claims, clients often need user profile information like name, email, or profile picture. OIDC standardizes this through additional scopes and the UserInfo endpoint.

Requesting Claims with Scopes

OIDC defines a set of standard scopes that map to sets of user claims. When a client includes these in its authorization request, it's signaling that it wants access to this information.

OpenID Connect explained

The 'OpenID Connect explained' article has a clear table mapping standard scopes to their associated claims.

Read section 7, 'Claims (user info)'. Pay attention to the table mapping scopes like profile and email to specific claims.

Your design should include a mapping service that translates requested scopes (profile, email, etc.) into a list of claims that the user has consented to share. The authorization server must then decide where to put these claims.

Architectural Choice: ID Token vs. UserInfo Endpoint

You have two standard ways to deliver these claims:

  1. Inside the ID Token: You can embed claims like name, email, etc., directly into the ID token's payload.

    • Pros: Fast. The client has all the information it needs immediately after token exchange, with no extra network calls.
    • Cons: Can lead to large, bloated tokens. The information is only as fresh as the token itself.
  2. Via the UserInfo Endpoint: You provide a separate API endpoint that returns the claims.

    • Pros: Keeps the ID token small and focused on authentication. The data is always fresh, as it's fetched on demand.
    • Cons: Requires an extra network call, adding latency.

A common and robust design is to include only the most essential claims in the ID token (like sub) and make all other profile information available via the UserInfo endpoint.

Designing the UserInfo Endpoint

The UserInfo endpoint is a simple, protected API that returns user claims.

OAuth 2.0 and OpenID Connect (in plain English)

The OktaDev video provides a concise explanation of the UserInfo endpoint's purpose.

Watch the short segment from 55:31 to 56:14. It describes how the UserInfo endpoint is used to get additional information not present in the ID token.

Here's how to architect it:

  • Endpoint: A standard endpoint, e.g., /userinfo.
  • Request: A GET or POST request to /userinfo.
  • Authorization: This is crucial. The request must be authenticated by including the access_token (received from the /token endpoint) in the Authorization: Bearer header. The UserInfo endpoint is a resource server, and the access token authorizes access to it.
  • Response: The endpoint validates the access token. If valid, it looks up the user (sub) and the scopes associated with the token. It then returns a JSON object containing only the claims the user consented to.

Example UserInfo response for an access token with openid and profile scopes:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "sub": "248289761001",
  "name": "Jane Doe",
  "given_name": "Jane",
  "family_name": "Doe",
  "preferred_username": "j.doe",
  "picture": "http://example.com/janedoe/me.jpg"
}

Conclusion

In this lesson, we have designed the complete OIDC layer on top of our OAuth 2.0 server, effectively turning it into a modern Identity Provider. We moved beyond simple authorization to build a system capable of handling secure, federated authentication.

Key Takeaways:

  • OIDC is an Authentication Layer on OAuth 2.0: It standardizes authentication by adding the openid scope, the ID Token, and the UserInfo endpoint.
  • ID Tokens Prove Authentication: The ID token is a signed JWT for the client that contains claims about the user and the authentication event. It asserts who the user is.
  • Access Tokens Authorize API Calls: The access token is for resource servers (like the UserInfo endpoint). It asserts what the client is allowed to do.
  • JWKS is Essential for Secure Key Distribution: The /.well-known/jwks.json endpoint is the standard, secure, and scalable mechanism for clients to get the public keys needed to validate ID tokens.
  • The UserInfo Endpoint Provides Additional Claims: It's a protected API, accessed with the access token, that delivers consented user attributes not included in the ID token.

Preview of the Next Lesson:

Now that we have a robust system for establishing a single, verified user identity, how do we leverage this across a landscape of multiple applications? In our next lesson, we will build upon our OIDC foundation to design advanced session management for federated identity, including single sign-on/sign-off (SSO) and channel-bound tokens. We will architect the systems that allow a user to log in once and gain access to many services seamlessly and securely.

Can't find a good explanation? Sign up and we'll make it for you

Sign up