Map OpenID Connect to OAuth 2.0
Map OpenID Connect to OAuth 2.0 — learn to align OIDC flows with OAuth 2.0 scopes and token endpoints.
Focus: map openid connect to oauth 2.0
You've mastered asking for permissions with OAuth 2.0, but something is missing: who actually is the user behind that access token? OAuth 2.0 was designed for authorization, not authentication, so trying to identify users with it alone leads to fragile hacks. In this lesson, you'll map OpenID Connect (OIDC) to OAuth 2.0 to see exactly how OIDC adds a secure identity layer on top of the authorization framework you already know, turning vague tokens into verifiable statements about who the user is.
The problem this lesson solves
OAuth 2.0 solves a very specific problem: granting a client access to protected resources on behalf of a resource owner. It does not define how to determine who that resource owner is. In fact, the OAuth specification explicitly says that using access tokens for authentication is out of scope and discouraged. Yet many developers, faced with building a login system, try to use the access token to fetch user info — only to run into trouble.
Consider what happens with a standard OAuth 2.0 flow:
- The client requests an access token with
scope=profile. - The provider returns an opaque token.
- The client calls the
/userinfoendpoint to get the user's name and email. - But the response format, scopes, and even the existence of that endpoint are not standardized.
Each provider behaves differently: some return a flat JSON object, others nest fields, and others require extra API calls. The client has no standard way to verify the token's audience or issuer. If you build your login around this, you've coupled your app to one provider's quirks — a nightmare for multi-provider support.
That's the problem: OAuth 2.0 lacks a standard for user identity. OpenID Connect fills that gap by defining a uniform way to request and validate identity claims, built directly on OAuth 2.0's plumbing.
Core concept / mental model
Think of OAuth 2.0 as the postal system: it reliably moves packages (tokens) from one place to another, but the packages can contain anything — letters, boxes, whatever. OIDC is the registered-mail service on top: it adds a standardized label (the ID token) and rules for what that label guarantees (the sender, the recipient, and the contents).
More formally, OpenID Connect is a thin layer that sits on top of OAuth 2.0. It uses the same endpoints, same redirects, and same token exchange, but it extends three core pieces:
- Scopes: OIDC introduces the
openidscope, which signals that the client wants identity information. - Token response: The token endpoint returns an additional ID token (a JWT) alongside the access token.
- Authentication: The authorization server authenticates the user during the flow, and the ID token contains verifiable claims about that user.
Key difference: authentication vs. authorization
OAuth 2.0 is about authorization — granting access to resources. OpenID Connect adds authentication — proving who the user is. You can have OAuth without OIDC, but OIDC always rides on OAuth. When you see openid in a scope list, you know the flow is doing both.
Visualizing the mapping
Here's a quick mental table that maps OIDC's additions onto OAuth 2.0's existing structure:
| OAuth 2.0 | OpenID Connect addition |
|---|---|
Authorization request (response_type=code) |
Adds scope=openid and optionally nonce |
| Authorization endpoint | Authenticates the user (consent screen) |
Token endpoint (code exchange) |
Returns id_token + access_token |
| Access token (opaque) | ID token (JWT with claims) |
| No standard user info | Standard /userinfo endpoint (optional) |
| No audience/issuer guarantees | iss, aud, exp claims in ID token |
The core insight: Every OIDC flow is an OAuth 2.0 flow — just with well-defined semantics for identity.
How it works step by step
To map OpenID Connect to OAuth 2.0, you need to see how the same steps you already know are extended. Let's walk through the Authorization Code Flow with OIDC (the most common scenario for web apps).
Step 1: The client sends an authorization request
This is almost identical to OAuth 2.0, but with two additions:
scopeincludesopenid(plus optional scopes likeprofileoremail).- A
nonceparameter is added to prevent replay attacks.
Example request to the authorization endpoint:
GET /authorize?response_type=code&client_id=my-client&redirect_uri=https://app.example.com/callback&scope=openid%20profile%20email&state=xyz&nonce=abc123
Step 2: The authorization server authenticates the user
If the user isn't already logged in, they see a login page. After login, they may see a consent screen. This is where OIDC's authentication happens — during the OAuth flow, not via a separate API.
Step 3: The client receives an authorization code
The provider redirects back to the redirect_uri with a code and the state value. This is pure OAuth.
Step 4: The client exchanges the code for tokens
The token endpoint request looks exactly like OAuth 2.0:
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=AUTH_CODE&redirect_uri=https://app.example.com/callback&client_id=my-client&client_secret=my-secret
Step 5: The response includes an ID token
Here's the big change — the response now contains id_token (a JWT) in addition to access_token and refresh_token:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ij..."
}
Step 6: The client validates the ID token
Unlike the access token (opaque), the ID token is a JWT that the client can verify locally using the provider's public keys. Validation includes:
- checking the
issclaim matches the expected issuer, - checking
audmatches your client ID, - checking
expis in the future, - verifying the signature,
- and ensuring the
noncematches what you sent.
The ID token contains claims like sub (subject = user's unique identifier), name, email, and more.
Step 7: (Optional) Fetch additional user info
If you need more claims, you can call the /userinfo endpoint with the access token. It's now standardized — same response format across providers.
That's it — the OAuth flow you already know, now with a standard identity layer.
Hands-on walkthrough
Let's put this into practice with a real OIDC provider. While you can use any provider, we'll use Google (or Auth0, or any OIDC-compliant provider). We'll simulate the flow with a Python script using the requests library and basic JWT decoding. This mirrors what a backend service might do after receiving a callback.
Prerequisites
- Python 3.10+ with
requestsandPyJWTinstalled (pip install requests PyJWT). - A registered OAuth/OIDC client (Google's OAuth playground, Auth0, etc.).
Example 1: Exchange authorization code for tokens and decode ID token
Here's a Flask-like handler that processes the callback and decodes the ID token. We'll use Google's token endpoint and fetch its JWKS for signature verification.
import requests
import jwt
from jwt import PyJWKClient
# Client credentials (from your app's registration)
CLIENT_ID = "your-client-id.apps.googleusercontent.com"
CLIENT_SECRET = "your-client-secret"
REDIRECT_URI = "https://app.example.com/callback"
# Authorization code from the redirect query parameter
code = "the-auth-code-you-received"
# 1. Exchange the code for tokens
token_response = requests.post(
"https://oauth2.googleapis.com/token",
data={
"code": code,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"redirect_uri": REDIRECT_URI,
"grant_type": "authorization_code",
}
)
tokens = token_response.json()
print("Keys in token response:", tokens.keys())
# 2. Validate the ID token
id_token = tokens["id_token"]
# Google exposes its JWKS at a fixed URL
jwks_url = "https://www.googleapis.com/oauth2/v3/certs"
jwks_client = PyJWKClient(jwks_url)
# Get the signing key for this token
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
# Decode and verify
claims = jwt.decode(
id_token,
signing_key.key,
algorithms=["RS256"],
audience=CLIENT_ID,
)
print("Verified claims:")
print(f" subject: {claims['sub']}")
print(f" email: {claims.get('email')}")
print(f" name: {claims.get('name')}")
Expected output:
Keys in token response: dict_keys(['access_token', 'expires_in', 'token_type', 'scope', 'refresh_token', 'id_token'])
Verified claims:
subject: 123456789012345678901
email: user@example.com
name: Jane Doe
Notice how the access token is not meant for your app to inspect — it's for calling APIs. The ID token is your proof of authentication.
Example 2: Calling the standard /userinfo endpoint
If you need additional claims beyond those in the ID token, you can fetch them from /userinfo, but that requires a valid access token. Here's the flow:
import requests
# Use the access_token from the token response
access_token = tokens["access_token"] # from the previous step
userinfo_response = requests.get(
"https://openidconnect.googleapis.com/v1/userinfo",
headers={"Authorization": f"Bearer {access_token}"}
)
if userinfo_response.status_code == 200:
userinfo = userinfo_response.json()
print(userinfo)
else:
print("Failed to fetch userinfo:", userinfo_response.text)
Expected output:
{
"sub": "123456789012345678901",
"name": "Jane Doe",
"email": "user@example.com",
"picture": "https://lh3.googleusercontent.com/..."
}
The /userinfo endpoint is standardized by OIDC, so the same code works with any OIDC provider (Auth0, Okta, Microsoft Entra ID, etc.) — just change the URL.
Example 3: Validating the nonce to prevent replay
If you sent a nonce in the authorization request, you must verify it in the ID token. Here's a minimal check:
# Store the nonce you generated earlier
nonce = "abc123" # from step 1
# After decoding the ID token (as above)
if claims.get("nonce") != nonce:
raise ValueError("Nonce mismatch! Replay attack suspected.")
else:
print("Nonce validated.")
This prevents an attacker from replaying an old ID token.
Pro tip: In real applications, never decode an ID token without verifying its signature. Use a well-tested JWT library and the provider's
jwks_uri(found in the OIDC discovery document).
Compare options / when to choose what
You might wonder: when should I use OIDC vs. plain OAuth? Here's a comparison of common approaches for handling user identity:
| Approach | What you get | Security | Use case |
|---|---|---|---|
| OAuth 2.0 (plain) | Access tokens, no identity | Low (no standard user info) | API authorization for machine-to-machine, delegated access |
| OIDC (ID token) | Identity + authorization | High (signed JWT) | Web and mobile app login, single sign-on |
OAuth + custom /userinfo |
Provider-specific user info | Medium (no signature validation) | Legacy integrations where OIDC isn't supported |
When to choose OIDC: Anyone who needs to identify users — which is almost every web, mobile, or SPA that has a login. Even if you're building a simple API that only needs a user ID, OIDC gives you a standard, secure way to get it.
When plain OAuth suffices: Pure machine-to-machine scenarios (service accounts, server-to-server) where there is no user to authenticate. Or when you're building an API that only cares about permissions (scopes) and doesn't need identity.
Variations: Some providers support OIDC but not all response_type combinations. For instance, the Implicit Flow (using response_type=id_token token) is deprecated in OAuth 2.1 but still seen in legacy SPAs. Also, PKCE is recommended for all OAuth flows, including OIDC, especially for mobile and public clients.
Troubleshooting & edge cases
Issue: invalid_grant when exchanging code
This usually means the authorization code was already used, expired, or doesn't match the redirect_uri. Make sure you:
- Exchange the code once and immediately.
- Use the exact
redirect_urithat was in the authorization request. - Check the
codeis not truncated or stale.
Issue: id_token fails signature validation
Possible causes:
- Using the wrong JWKS URL (providers sometimes host multiple keys).
- Clock skew between your server and the provider.
- Using an asymmetric algorithm (RS256) but trying to verify with a symmetric key.
Fix: Fetch the JWKS from the provider's discovery document (e.g., https://provider/.well-known/openid-configuration) and always specify the algorithms parameter in jwt.decode().
Issue: nonce missing from decoded claims
Some providers may not include a nonce if you didn't send one. Make sure you include nonce in the authorization request and verify it. If you're using a library like jwt.decode(), you may need to manually check the nonce because it's not a standard JWT claim.
Edge case: Access token vs. ID token for APIs
Never use an ID token as an access token. ID tokens are short-lived, contain identity claims, and are not meant for API authorization. Always use the access_token to call protected resources.
Edge case: sub is the only guaranteed claim
The sub claim is the stable identifier for the user within your client's context. Don't rely on email or name being present — they depend on which scopes were requested.
What you learned & what's next
You've now mapped OpenID Connect to OAuth 2.0 and understand exactly how OIDC extends the framework you already know. You can explain why OIDC adds the openid scope, where the ID token comes from, how to validate it, and when to use the /userinfo endpoint. You've also completed a hands-on exercise that exchanges an auth code for tokens and validates the ID token's signature and claims — directly meeting the lesson's objectives.
This is a critical mental model because everything else in auth — PKCE, token refresh, session management — builds on this foundation. Next up, you'll dive into ID token validation in depth, covering algorithm selection, audience checks, and how to handle clock skew. You'll also explore PKCE for mobile/SPA flows, and finally token refresh strategies to keep users signed in securely.
From here, you can confidently integrate any OIDC provider into your stack, knowing you have a standardized, secure identity layer. The next lesson will make you even more precise about what you check in that ID token before you trust it.
Practice recap
Try this: register an OIDC client with a provider (e.g., Auth0), run through the authorization code flow with a public client, and print both the ID token claims and the userinfo response. Then alter the nonce and see the validation fail. This will solidify the mapping from OAuth 2.0's code exchange to OIDC's identity verification.
Common mistakes
- Using the access token to identify users. The access token is for API calls; the ID token is for identity. Never mix them.
- Forgetting to validate the ID token's signature and issuer. Always fetch the JWKS and verify
iss,aud, andexp. - Skipping the
noncecheck. Without it, you're vulnerable to replay attacks. - Assuming the
/userinfoendpoint exists or has a standard response in plain OAuth 2.0. It's only standardized in OIDC.
Variations
- Hybrid Flow: Mix Authorization Code and Implicit by requesting
response_type=code id_token. Useful for native apps that need an ID token immediately. - Different response modes:
form_postreturns tokens via HTML form POST instead of a query string, useful for high-security flows. - Provider-specific
/userinfoendpoints exist in plain OAuth 2.0, but they lack standardization. OIDC forces consistency.
Real-world use cases
- Implementing 'Login with Google' or 'Sign in with GitHub' on a web app — the
openidscope and ID token handle user identity. - Building a microservices API where each service needs to know the user who made the request; OIDC ID tokens carry verified claims.
- Single sign-on (SSO) across multiple apps in an organization — OIDC acts as the identity layer on top of the OAuth 2.0 authorization server.
Key takeaways
- OpenID Connect is a thin layer on top of OAuth 2.0 that adds standardized authentication using the
openidscope and an ID token. - The ID token is a signed JWT containing claims about the user, and it must be validated (signature, issuer, audience, expiration).
- OAuth 2.0 is for authorization; OIDC adds authentication. You need OIDC whenever you need to identify the user.
- The
/userinfoendpoint is standardized in OIDC, but it's optional; the ID token often has enough claims. - Always send and verify a
noncein the authorization request to prevent replay attacks.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.