OAuth 2.0 and OpenID Connect (OIDC) are the two protocols that power modern authentication and authorization on the web. Every time a user clicks "Sign in with Google," every time a mobile app requests access to a user's calendar, every time a microservice calls another microservice with a bearer token — OAuth 2.0 or OIDC is running underneath.
Understanding these protocols is not optional for GRC engineers. They appear in every cloud-native architecture, every API security review, and every modern SSO configuration you will encounter. This lesson covers what each protocol does, how the major grant types work, what the tokens look like, and what you need to verify during an audit.
This is the single most important thing to understand, and it is where most people get confused.
OAuth 2.0 is an authorization framework. It answers the question: "Is this application allowed to access this resource on behalf of this user?" OAuth issues access tokens that grant scoped permissions. An access token might say "this application can read the user's Google Drive files" — but the access token does not tell you who the user is.
OpenID Connect (OIDC) is an authentication layer built on top of OAuth 2.0. It answers the question: "Who is this user?" OIDC adds an ID token — a signed JWT containing the user's identity claims — to the OAuth flow. With OIDC, the application learns both who the user is (the ID token) and what the application is authorized to do on their behalf (the access token).
Think of it this way: OAuth 2.0 gives you a key to a room. OIDC gives you a key and a name badge.
In practice, most modern SSO implementations use OIDC, not raw OAuth 2.0, because applications need to know who the user is. But many API authorization scenarios use OAuth 2.0 without OIDC, because the API only cares whether the caller has permission — not who the human behind the request is.
OAuth 2.0 defines several "grant types" — different flows for different scenarios. Each grant type describes how the client application obtains an access token from the authorization server. The right grant type depends on the client type (web app, mobile app, server, CLI tool) and whether a user is involved.
This is the standard flow for web applications, mobile apps, and any scenario where a user is present. It is the grant type you will encounter most often.
PKCE — Proof Key for Code Exchange, pronounced "pixy" — is a security extension that prevents authorization code interception attacks. It was originally designed for mobile apps (which cannot securely store client secrets) but is now recommended for all clients, including server-side web apps.
User (Browser) Client App Authorization Server Resource API
│ │ │ │
1. │── Click "Login" ─►│ │ │
│ │ │ │
2. │ │── Generate: │ │
│ │ code_verifier (random) │ │
│ │ code_challenge = │ │
│ │ SHA256(code_verifier) │ │
│ │ │ │
3. │◄── 302 Redirect ──│ │ │
│ to /authorize │ │ │
│ ?response_type=code │ │
│ &client_id=... │ │ │
│ &redirect_uri=...│ │ │
│ &scope=openid profile email │ │
│ &code_challenge=... │ │
│ &code_challenge_method=S256 │ │
│ │ │ │
4. │──── Browser navigates to auth server ──────►│ │
│ │ │ │
5. │◄─── Login page + consent screen ────────────│ │
│ │ │ │
6. │──── User authenticates + consents ─────────►│ │
│ │ │ │
7. │◄─── 302 Redirect to redirect_uri ───────────│ │
│ ?code=AUTH_CODE │ │ │
│ │ │ │
8. │──── Follows redirect ►│ │ │
│ │ │ │
9. │ │── POST /token ─────────►│ │
│ │ grant_type= │ │
│ │ authorization_code │ │
│ │ code=AUTH_CODE │ │
│ │ code_verifier=... │ │
│ │ │ │
10. │ │◄── 200 OK ──────────────│ │
│ │ { access_token, │ │
│ │ id_token, │ │
│ │ refresh_token } │ │
│ │ │ │
11. │ │── GET /api/resource ─────────────────────────► │
│ │ Authorization: │ │
│ │ Bearer {access_token} │ │
│ │ │ │
12. │◄── Logged in ──────│◄── 200 OK + data ──────────────────────────── │OAuth 2.0 Authorization Code flow with PKCE: the client generates a code verifier, exchanges it during token retrieval, preventing code interception
The key security property of PKCE: in step 9, the client proves it is the same party that initiated the flow in step 3 by presenting the code_verifier that matches the code_challenge. Even if an attacker intercepts the authorization code in step 7 (through a malicious app registered to the same redirect URI, or through a compromised browser extension), they cannot exchange it for tokens without the code verifier.
This is the machine-to-machine flow. No user is involved. A backend service needs to call another service or API, and it authenticates using its own credentials — a client ID and client secret (or a signed JWT assertion).
The flow is simple: the client sends its credentials directly to the token endpoint and receives an access token. There is no browser redirect, no user consent, no authorization code.
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=backend-service-abc
&client_secret=s3cr3t-k3y-v4lu3
&scope=api.read api.write
The authorization server validates the credentials, checks that the requested scopes are allowed for this client, and returns an access token. The client uses that token to call the target API.
For GRC purposes, client credentials flows create audit obligations: the client secrets must be rotated on a schedule, stored securely (not hardcoded in source code), scoped to minimum necessary permissions, and monitored for anomalous usage. Every client credential is a machine identity that requires the same governance as a human identity.
This flow is for devices with limited input capabilities — smart TVs, IoT devices, CLI tools — where the user cannot easily type a URL or enter credentials on the device itself.
The device displays a short code (like ABCD-1234) and a URL (like https://auth.example.com/device). The user visits that URL on their phone or laptop, enters the code, and authenticates normally. Meanwhile, the device polls the authorization server until the user completes authentication, at which point the device receives its tokens.
You see this flow when pairing a streaming device, authorizing a GitHub CLI tool, or connecting a smart home device to a cloud account.
The Implicit flow was designed for single-page applications (SPAs) in an era when browsers could not make cross-origin POST requests. The flow returned the access token directly in the URL fragment — #access_token=xyz — without an intermediate authorization code exchange.
This is deprecated because tokens in URL fragments are logged in browser history, potentially leaked through the Referer header, and exposed to JavaScript on the page. The Implicit flow also cannot issue refresh tokens, so the application must redirect the user to the authorization server every time the access token expires.
Modern SPAs should use Authorization Code + PKCE instead. If you encounter an application still using the Implicit flow, flag it as a security finding.
When OIDC is used, the authorization server returns an ID token — a JSON Web Token (JWT) containing the authenticated user's identity claims. Here is a decoded example:
{
"iss": "https://auth.example.com",
"sub": "user_8f2a9c4d1e6b",
"aud": "client_app_abc123",
"exp": 1748358600,
"iat": 1748355000,
"nonce": "n-0S6_WzA2Mj",
"email": "alice@example.com",
"email_verified": true,
"name": "Alice Chen",
"given_name": "Alice",
"family_name": "Chen",
"groups": ["Engineering", "Production-Access"]
}
The key claims:
email, this should never change even if the user updates their profile.client_app_abc123 should be rejected by client_app_xyz789.Quick check
A React application stores OAuth access tokens in localStorage to persist them across page refreshes. What is the primary security concern?
GRC Engineer's focus
When reviewing an OAuth 2.0 or OIDC implementation, verify these specific configurations:
Token lifetimes — Access tokens should be short-lived (15-60 minutes). Long-lived access tokens expand the attack window if stolen. Refresh tokens should have a longer lifetime but must be rotated on each use (rotation policy).
Refresh token rotation — When a refresh token is used to obtain a new access token, the authorization server should issue a new refresh token and invalidate the old one. This limits the damage from refresh token theft — the attacker and the legitimate client cannot both use the same refresh token chain.
Scope restrictions — Access tokens should carry only the minimum scopes needed for the application's functionality. An application that only needs to read user profile data should not request admin:write scopes. Review the allowed scopes for each registered client.
PKCE enforcement — The authorization server should require PKCE for all public clients (SPAs, mobile apps) and ideally for all clients. If PKCE is optional, an attacker can initiate a flow without it and intercept the authorization code.
Token storage — Tokens should never be stored in localStorage in browser applications. For server-side applications, tokens should be stored encrypted at rest. Client secrets should be in secure vaults, not in source code or environment variables in CI/CD logs.
Access tokens are bearer tokens — anyone who possesses the token can use it. If an attacker obtains an access token through XSS, network interception, or log exposure, they can call APIs as the legitimate user until the token expires. Short token lifetimes and token binding (DPoP — Demonstrating Proof-of-Possession) are the primary mitigations.
Over time, applications tend to request broader scopes than they need, and administrators approve them without reviewing what access they grant. A quarterly review of registered OAuth clients and their approved scopes is critical — this is the OAuth equivalent of a least-privilege access review.
When users are prompted to approve OAuth consent screens frequently, they stop reading the permissions and click "Allow" reflexively. This is how malicious applications obtain broad permissions through phishing campaigns that mimic legitimate OAuth flows. For enterprise environments, pre-approving trusted applications and blocking unknown clients at the authorization server level mitigates this risk.
Quick check
A microservice needs to call an internal payment API to process refunds. No user is involved in the transaction. Which OAuth 2.0 grant type is appropriate?
OAuth 2.0 and OIDC are the modern foundation of authentication and authorization on the web. OAuth handles authorization — scoped access tokens that grant applications permission to act on behalf of users or as themselves. OIDC handles authentication — ID tokens that prove who the user is. The Authorization Code + PKCE flow is the standard for user-facing applications. Client Credentials is the standard for machine-to-machine. Implicit is deprecated. Device Code handles constrained devices.
As a GRC engineer, your job is not to implement these protocols — it is to verify that they are implemented correctly: short token lifetimes, refresh token rotation, PKCE enforcement, minimum scopes, secure token storage, and no deprecated flows in production. Every one of these configurations is an auditable control.
The next lesson provides visual flow diagrams for each protocol so you can see the differences at a glance.