OAuth 2.0 and OIDC: Delegated Authorization and Identity


You are building an app that needs to read a user’s Google Calendar. You could ask the user for their Google password. But then you have their password. If your app is compromised, their Google account is compromised. And Google has no way to let the user revoke your access without changing their password.

OAuth 2.0 solves this. The user grants your app permission to access their calendar. Google issues your app a token. Your app uses the token. The user can revoke the token at any time without changing their password. Your app never sees the password.

OAuth 2.0: delegated authorization

OAuth 2.0 is an authorization framework. It allows a user to grant a third-party application limited access to their resources on another service, without sharing their credentials.

Key roles:

  • Resource owner: The user who owns the data
  • Client: The application requesting access
  • Authorization server: Issues access tokens (Google, GitHub, your auth service)
  • Resource server: Hosts the protected resources (Google Calendar API)

Key concepts:

  • Access token: A credential that grants access to specific resources
  • Refresh token: A long-lived credential used to get new access tokens
  • Scope: What the access token is allowed to do (calendar.read, email)
  • Authorization code: A short-lived code exchanged for tokens (prevents tokens from appearing in browser history)

The Authorization Code Flow

The most common and secure OAuth flow for web applications.

graph TB
subgraph authcode["Authorization Code Flow"]
  U["User"] -->|"1. Click Login with Google"| APP["Your app"]
  APP -->|"2. Redirect to Google
with client_id, scope, redirect_uri"| GOOGLE["Google Auth Server"]
  GOOGLE -->|"3. Show consent screen"| U
  U -->|"4. Grant permission"| GOOGLE
  GOOGLE -->|"5. Redirect back with code"| APP
  APP -->|"6. Exchange code for tokens
(server-to-server)"| GOOGLE
  GOOGLE -->|"7. Access token + refresh token"| APP
  APP -->|"8. API call with access token"| GCAL["Google Calendar API"]
  GCAL -->|"9. Calendar data"| APP
end

style GOOGLE fill:#EEEDFE,stroke:#534AB7,color:#3C3489
style APP fill:#E1F5EE,stroke:#0F6E56,color:#085041
style GCAL fill:#FAEEDA,stroke:#854F0B,color:#633806

Why the code exchange? The authorization code is short-lived and single-use. It appears in the browser URL (visible in history, logs). The actual token exchange happens server-to-server, keeping tokens out of the browser.

PKCE (Proof Key for Code Exchange): For mobile apps and SPAs that cannot keep a client secret, PKCE adds a code verifier/challenge to prevent authorization code interception attacks.

OAuth flows

Authorization Code Flow

For web apps with a backend server. Most secure. Use PKCE for added security.

Authorization Code + PKCE

For mobile apps and SPAs. No client secret needed. The code verifier proves the app that started the flow is the same one completing it.

Client Credentials Flow

For machine-to-machine (M2M) communication. No user involved. The client authenticates with its own credentials and gets an access token. Used for microservice-to-microservice auth.

Device Authorization Flow

For devices with limited input (smart TVs, CLI tools). The device shows a code. The user enters it on another device (phone, computer). The device polls for the token.

OIDC: identity on top of OAuth

OAuth 2.0 is about authorization (what can you do?). It does not tell you who the user is. OpenID Connect (OIDC) adds identity (who are you?) on top of OAuth 2.0.

OIDC adds:

  • ID token: A JWT containing the user’s identity (sub, email, name, picture)
  • UserInfo endpoint: Returns additional user claims
  • Standard scopes: openid (required), profile, email, address, phone
  • Discovery endpoint: /.well-known/openid-configuration - describes the provider’s endpoints and capabilities

When to use OAuth vs OIDC:

  • OAuth 2.0: You need to access a user’s resources on another service (read their calendar, post to their Twitter)
  • OIDC: You need to authenticate the user (log them in to your app using their Google/GitHub account)

In practice, most “Login with Google” implementations use OIDC. The ID token tells you who the user is. The access token lets you call Google APIs on their behalf.

graph LR
subgraph oidc["OIDC vs OAuth"]
  OAUTH["OAuth 2.0
Authorization
Access token
What can you do?"]
  OIDC2["OIDC
Authentication
ID token + access token
Who are you?"]
  OAUTH -->|"extends"| OIDC2
end

subgraph tokens["Token Types"]
  AT["Access token
Opaque or JWT
For API calls
Short-lived"]
  IT["ID token
Always JWT
User identity
Short-lived"]
  RT["Refresh token
Opaque
Get new access tokens
Long-lived"]
end

style OAUTH fill:#EEEDFE,stroke:#534AB7,color:#3C3489
style OIDC2 fill:#E1F5EE,stroke:#0F6E56,color:#085041
style AT fill:#FAEEDA,stroke:#854F0B,color:#633806
style IT fill:#EEEDFE,stroke:#534AB7,color:#3C3489
style RT fill:#F1EFE8,stroke:#888780,color:#444441

Where it breaks or gets interesting

Token leakage in the implicit flow

The implicit flow (now deprecated) returned tokens directly in the URL fragment. Tokens appeared in browser history, server logs, and referrer headers. The authorization code flow with PKCE replaced it.

Never use the implicit flow. Use authorization code + PKCE for SPAs and mobile apps.

Scope creep

Applications often request more scopes than they need. Users grant broad permissions without understanding what they are allowing. Best practice: request the minimum scopes needed. Request additional scopes only when needed (incremental authorization).

Token validation

When your API receives an access token, you must validate it:

  1. Verify the signature (if JWT) or call the introspection endpoint (if opaque)
  2. Check the expiry (exp claim)
  3. Check the issuer (iss claim) - must be your authorization server
  4. Check the audience (aud claim) - must include your API
  5. Check the required scopes

Skipping any of these checks is a security vulnerability.

The state parameter

The state parameter in the authorization request is a CSRF protection mechanism. Your app generates a random value, includes it in the authorization request, and verifies it when the authorization code is returned. Without it, an attacker can trick a user into completing an OAuth flow that the attacker initiated.

Always use the state parameter.

Real-world systems

Google Identity - OIDC provider. Used for “Sign in with Google.” Supports all standard OAuth flows. JWKS endpoint for public key distribution.

GitHub OAuth - Used for “Sign in with GitHub.” Supports authorization code flow. Used by many developer tools.

Auth0 - Identity platform. Supports OAuth 2.0 and OIDC. Handles user management, social login, MFA, and more.

Okta - Enterprise identity platform. OAuth 2.0 and OIDC. Used for SSO in enterprise applications.

AWS Cognito - Managed identity service. Supports OAuth 2.0 and OIDC. Integrates with AWS services.

Keycloak - Open-source identity and access management. Self-hosted alternative to Auth0/Okta.

How to apply it in practice

Implementing “Login with Google”

  1. Register your app with Google (get client_id and client_secret)
  2. Redirect users to Google’s authorization endpoint with scope=openid email profile
  3. Google redirects back with an authorization code
  4. Exchange the code for tokens (server-to-server)
  5. Validate the ID token (verify signature, check iss, aud, exp)
  6. Extract the user’s identity from the ID token (sub, email, name)
  7. Create or update the user in your database
  8. Issue your own session token or JWT

Building your own OAuth server

If you are building an API that other applications will access on behalf of users:

  1. Use an existing library (Authlib for Python, Spring Security OAuth for Java)
  2. Implement the authorization code flow with PKCE
  3. Issue short-lived access tokens (15 minutes) and long-lived refresh tokens
  4. Publish a JWKS endpoint for public key distribution
  5. Implement a token introspection endpoint for opaque tokens
  6. Implement token revocation

Microservice authentication with client credentials

For service-to-service authentication:

  1. Each service has a client_id and client_secret
  2. Service A requests a token from the auth server using client credentials
  3. Auth server issues an access token with the service’s identity and allowed scopes
  4. Service A includes the token in requests to Service B
  5. Service B validates the token (checks iss, aud, scope)

FAQ

Q: What is the difference between OAuth 2.0 and OAuth 1.0?

OAuth 1.0 required cryptographic signing of every request (complex). OAuth 2.0 uses bearer tokens over HTTPS (simpler). OAuth 2.0 is not backward compatible with OAuth 1.0. OAuth 2.0 is the current standard. OAuth 1.0 is obsolete.

Q: Should access tokens be JWTs or opaque tokens?

JWTs allow stateless validation (no database lookup). Opaque tokens require a database lookup or introspection endpoint call. Use JWTs for access tokens when: you have many resource servers that need to validate tokens independently, you want to avoid a central introspection endpoint. Use opaque tokens when: you need immediate revocation, you do not want to expose user claims in the token, or you are using a third-party auth provider that issues opaque tokens.

Q: What is the difference between authentication and authorization?

Authentication (AuthN) is verifying who you are: “Are you really Alice?” Authorization (AuthZ) is verifying what you can do: “Is Alice allowed to delete this resource?” OAuth 2.0 is primarily about authorization. OIDC adds authentication. In practice, most systems need both: authenticate the user (OIDC), then authorize their actions (OAuth scopes, RBAC, ABAC).

Interview questions

Q1: A user logs in to your app using “Sign in with Google.” Walk through the complete flow.

Strong answer: User clicks “Sign in with Google.” Your app generates a random state value and stores it in the session. Your app redirects the user to Google’s authorization endpoint with: client_id, redirect_uri, scope (openid email profile), response_type=code, state. Google shows a consent screen. User approves. Google redirects back to your redirect_uri with: code and state. Your app verifies the state matches what it stored (CSRF protection). Your app makes a server-to-server POST to Google’s token endpoint with: code, client_id, client_secret, redirect_uri, grant_type=authorization_code. Google returns: id_token, access_token, refresh_token. Your app validates the id_token: verify the RS256 signature using Google’s public keys (from JWKS endpoint), check iss is accounts.google.com, check aud is your client_id, check exp is in the future. Extract the user’s sub (unique Google ID), email, and name. Create or update the user in your database. Issue your own session token.

Q2: How do you implement service-to-service authentication in a microservices architecture?

Strong answer: Use the OAuth 2.0 client credentials flow. Each service has a client_id and client_secret registered with the auth server. When Service A needs to call Service B: Service A requests a token from the auth server using its client credentials. The auth server issues a JWT access token with Service A’s identity and the scopes it is allowed. Service A includes the token in the Authorization header when calling Service B. Service B validates the token: verify the signature, check iss, check aud (must include Service B’s identifier), check the required scope. This is stateless (no database lookup for validation) and auditable (the token contains the caller’s identity). Rotate client secrets regularly. Use short-lived tokens (15 minutes) with automatic refresh.

Q3: What is PKCE and why is it needed for SPAs?

Strong answer: PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. Without PKCE: a malicious app on the same device could register the same redirect URI and intercept the authorization code. With PKCE: before starting the flow, the app generates a random code_verifier and computes code_challenge = SHA256(code_verifier). The app includes code_challenge in the authorization request. When exchanging the code for tokens, the app includes code_verifier. The auth server verifies that SHA256(code_verifier) matches the code_challenge it received earlier. Only the app that started the flow knows the code_verifier, so even if the code is intercepted, it cannot be exchanged for tokens. SPAs and mobile apps cannot keep a client_secret (it would be visible in the source code or binary), so PKCE is the security mechanism that replaces the client_secret.