Teams still build login on plain OAuth 2.0 and treat an access token as proof of who the user is, which is exactly the gap OpenID Connect was created to close. OAuth 2.0 (RFC 6749) is a delegated authorisation framework: it lets a client obtain limited access to an API. OpenID Connect (OIDC) is an identity layer on top of it that adds a signed ID token, a standard user identifier and a UserInfo endpoint, so a client can learn who signed in and when. This guide gives the short answer, a side-by-side table, decision guidance for login, API access and machine-to-machine traffic, the security trade-offs of each, and how to move an application from OAuth pseudo-authentication to OIDC. It is for application developers, identity architects and API owners. The protocol basics live in the OAuth 2.0 and OpenID Connect Guide; this page focuses on the choice between them.
Key takeaways
- OAuth answers "what may this client do", OIDC answers "who is the user". RFC 6749 section 3.1 places resource owner authentication out of scope; OIDC Core defines how the provider tells the client about the authentication event.
- The ID token is for the client, the access token is for the API. The client validates the ID token (issuer, audience, signature, expiry, nonce); the API validates the access token. Swapping their roles is the root of most mistakes.
- OIDC is not an alternative to OAuth, it runs on it. Every OIDC login is an OAuth authorisation code flow with the
openidscope, so OAuth security rules from RFC 9700 apply in full. - Machine-to-machine traffic needs OAuth only. The client credentials grant has no end user, so there is nothing for an ID token to describe.
- Using an access token as login proof invites token injection. RFC 6749 section 10.16 and RFC 9700 section 4.6.1 describe how a token issued to one client can be replayed into another; the OIDC
aud,nonceandat_hashchecks are the defence.
The short answer
Use OIDC whenever an application needs to sign a person in, establish a session or read a verified user identifier, including single sign-on across web, mobile and native apps. Use OAuth 2.0 alone when a client needs to call an API and does not need to know who the user is, and for every machine-to-machine case (client credentials, token exchange, workload federation). Most real applications use both in the same flow: one authorisation code request with scope=openid profile api.read returns an ID token that the client uses to log the user in and an access token that it sends to the API.
The question is rarely "OAuth or OIDC". It is "which token proves what, and who validates it". Get that right and the protocol choice follows.
What each protocol actually defines
OAuth 2.0: delegated authorisation
RFC 6749 defines four roles (resource owner, client, authorisation server, resource server) and grants for obtaining an access token: authorisation code (section 4.1), implicit (4.2), resource owner password credentials (4.3) and client credentials (4.4), plus extension grants (4.5). The access token is "usually opaque to the client" (section 1.4) and its format is left to companion specifications. The framework authenticates the client (section 2.3) but says how the authorisation server authenticates the user is out of scope. An access token therefore tells an API that someone authorised this client for these scopes. It does not tell the client who that someone is, how they signed in or when.
OpenID Connect: an identity layer
OIDC Core 1.0 describes itself as a simple identity layer on top of OAuth 2.0. It adds:
- The
openidscope: requests must contain it (section 3.1.2.1); without it the request is a plain OAuth request. - The ID token: a JWT that must be signed, with required claims
iss,sub,aud,expandiat, plusauth_timewhenmax_ageis requested,noncewhen the client sent one, and optionalacr,amrandazp(section 2). - A stable user identifier: the pair
issplussub. Providers may issue pairwise subject identifiers so the same user has differentsubvalues at different clients. - The UserInfo endpoint: a protected resource that returns claims about the user when called with an access token (section 5.3).
- Discovery and dynamic registration: metadata at
/.well-known/openid-configuration, including thejwks_uriused to verify ID token signatures. RFC 8414 later generalised this metadata for plain OAuth at/.well-known/oauth-authorization-server. - Session features:
prompt,max_ageandacr_valuesto force re-authentication or request an assurance level, and logout specifications. See the OpenID Connect Logout and Session Management Guide.
Side-by-side comparison
| Property | OAuth 2.0 | OpenID Connect |
|---|---|---|
| Purpose | Delegated access to APIs | User authentication and identity claims for the client |
| Core specification | RFC 6749, RFC 6750, security BCP RFC 9700 | OpenID Connect Core 1.0, Discovery 1.0, plus all OAuth rules |
| Main artefact | Access token (format not defined by RFC 6749) | ID token (always a signed JWT) alongside the access token |
| Audience of the token | The resource server (API) | The client (relying party); aud must contain its client_id |
| Who validates | The API, by signature check or introspection | The client, per OIDC Core section 3.1.3.7 |
| User identifier | None defined | iss + sub, optionally pairwise |
| Authentication context | None defined | auth_time, acr, amr |
| Replay and injection binding | state, PKCE | Adds nonce and at_hash bound into the signed ID token |
| Metadata discovery | RFC 8414 oauth-authorization-server | openid-configuration |
| Works without an end user | Yes (client credentials, token exchange, JWT bearer) | No; it describes a user authentication event |
| Typical consumers | APIs, gateways, service clients, integrations | Web apps, mobile and native apps, SSO portals |
Choosing: decision guidance by scenario
| Scenario | Use | Why |
|---|---|---|
| Web app signs users in through a corporate or social identity provider | OIDC, authorisation code flow with PKCE | Needs a verified user identifier and authentication time |
| Single-page app signs in and calls its own API | OIDC through a backend-for-frontend, access token kept server side | Keeps tokens out of browser storage. See the OAuth for Browser-Based Applications and the BFF Pattern Guide |
| Mobile or desktop app with login and API calls | OIDC, code flow with PKCE via the system browser | RFC 9700 requires PKCE for public clients. See the OAuth for Native and Mobile Apps Guide |
| Third-party app reads a user's calendar; it does not care who the user is | OAuth 2.0 authorisation code | Delegated access is the whole requirement |
| Service calls another service with no user | OAuth 2.0 client credentials | No end user, so no ID token. See the Machine-to-Machine OAuth (Client Credentials) Guide |
| API needs to know which user a request acts for | OAuth access token with sub (for example an RFC 9068 JWT access token) | The API reads identity from a token issued for it, never from an ID token |
| CI job or cloud workload proves its identity to a cloud | OIDC-format tokens from the platform issuer exchanged under OAuth or cloud STS | Workload federation reuses OIDC token formats with no human. See the Workload Identity Federation Guide |
| Enterprise SSO where the application only supports SAML | SAML, or a broker that speaks both | See the SAML vs OIDC Guide |
Small organisations: adopt OIDC from a managed identity provider and avoid writing token validation by hand; a certified relying party library handles the section 3.1.3.7 checks. Large organisations: standardise one OIDC profile (allowed flows, required claims, assurance values, token lifetimes) for all internal apps, and a separate OAuth profile for APIs and machine clients, so reviewers check each integration against a known baseline.
Security trade-offs
What OIDC adds that plain OAuth lacks
- Audience binding to the client: the client rejects any ID token whose
auddoes not contain itsclient_id. A plain access token offers the client no standard way to know it was issued for that client, which is the impersonation case in RFC 6749 section 10.16. - Injection detection: RFC 9700 section 4.6.1 notes that pure OAuth flows cannot detect access token injection, while the ID token's
at_hashandnoncelet the client tie the response to its own request. - Mix-up defence: the
issclaim in the ID token lets a client that trusts several providers confirm which one answered. Plain OAuth clients need the RFC 9207issauthorisation response parameter for the same protection. - Assurance signals:
acrandamrtell the client whether phishing-resistant MFA was used, so step-up decisions can be made. See the OAuth Step-Up Authentication Challenge Guide.
What OIDC adds to the attack surface
- Signature validation code: every client now verifies JWTs, which brings algorithm confusion, missing issuer checks and stale JWKS caches into the client. See the JWT Security Guide.
- Personal data in tokens: ID tokens and UserInfo responses carry email, name and other claims that end up in logs and browser storage. Request only the scopes the client uses.
- Federation trust: trusting an external OIDC issuer means trusting its signing keys and its user verification. A compromised issuer key forges logins at every relying party. See the Identity Federation Failure Modes.
Rules that apply to both
Because OIDC runs on OAuth, RFC 9700 governs both: use the authorisation code flow, not the implicit grant; never use the resource owner password credentials grant; use PKCE with S256 (mandatory for public clients); match redirect URIs exactly; restrict access tokens to an audience and the minimum scope; and sender-constrain tokens with mTLS (RFC 8705) or DPoP (RFC 9449) where theft is a realistic risk. See the OAuth 2.0 Security Best Current Practice (RFC 9700) Guide and the PKCE Implementation guide.
Implementing OIDC login correctly
- Load provider metadata from discovery and confirm the
issuervalue is identical to the issuer you configured; pin the expected issuer in configuration rather than trusting whatever the metadata says. - Send the authorisation request with
response_type=code,scope=openidplus only the scopes needed, a freshstate, a freshnonceand a PKCEcode_challengeusingS256. - Redeem the code at the token endpoint over TLS, authenticating a confidential client with
private_key_jwtor mTLS rather than a shared secret where the provider supports it. See the JWT Client Authentication (private_key_jwt) Guide. - Validate the ID token per OIDC Core section 3.1.3.7: exact
issmatch,audcontains yourclient_id, signature with a key from the provider'sjwks_uriand an allowed algorithm,expin the future,nonceequal to the value you sent, andauth_timeoracrif your policy requires them. - Key the local account on
iss+sub, never on email address; email can change or be reassigned and is not unique across issuers. - Create your own session and treat the ID token as a one-time proof, not as a session cookie. Discard it or store it only for logout hints.
- Send only the access token to APIs. APIs validate it for their own audience and scopes.
Migrating from OAuth pseudo-authentication to OIDC
Many older integrations "log in" by obtaining an access token, calling a profile API and trusting whatever user it returns. That pattern cannot detect a token issued to a different client. A safe migration:
- Inventory: list every client registration whose sign-in flow lacks the
openidscope, and every application that derives user identity from an API response or an access token it did not validate for itself. - Enable OIDC on the existing registration in a test tenant; most providers add the
openidscope without a new client. - Run in shadow mode: keep the old login path but also validate the ID token and log any mismatch between the old user identifier and
iss+sub. - Map accounts: store
iss+subagainst existing local accounts, resolving mismatches manually rather than by email alone. - Switch identity to the ID token for a pilot group, then for all users, with a feature flag that can return to the old path for one release.
- Remove the fallback and any implicit grant or password grant configuration once telemetry shows no use. See the OAuth Implicit Flow Migration guide.
Where identity fits
For non-human identities the choice is simpler: workloads, CI jobs and AI agents acting on their own account use OAuth grants (client credentials, token exchange, JWT bearer) and never need an ID token. OIDC still matters to them in two ways. First, platforms such as CI systems and Kubernetes issue OIDC-format JWTs that cloud providers accept for federation, so the issuer, audience and subject checks in this guide reappear as trust policy conditions. Second, agents acting on behalf of a signed-in user should carry the user's identity through OAuth token exchange, not by forwarding the user's ID token. See the OAuth Token Exchange and Delegation Guide.
Threat model, failure modes and a worked scenario for each option
OAuth 2.0 on its own
Threat model. The asset is the access token, and the attacker wants to steal, replay or inject one. RFC 9700 addresses this directly: clients should not use the implicit grant, public clients must use PKCE, and the resource owner password credentials grant must not be used at all. Failure modes. The classic failure is treating possession of an access token as proof of who the user is. An access token tells an API what the caller may do, not who logged in, so a token issued to one client can be presented to a second application that wrongly accepts it as a login. Controls. Audience-restrict access tokens to a specific resource server, as RFC 9700 recommends, and for public clients make refresh tokens sender-constrained or rotate them, which RFC 9700 makes mandatory. Decision criteria. Use OAuth alone when the only question is whether this client may call this API, for example a service calling another service with the client credentials grant.
Worked scenario. A reporting job needs to read an internal billing API. There is no user, so there is nothing to log in. The job uses the client credentials grant, receives a token whose audience is the billing API only, and the API rejects any token minted for another audience. Adding OIDC here would add an ID token that nobody reads.
OpenID Connect on top of OAuth
Threat model. The asset is the authenticated session. The attacker wants to log in as someone else by injecting or replaying an ID token, or by mixing up responses from two identity providers. Failure modes. Relying parties that skip validation of the ID token issuer, audience, expiry and nonce, or that fetch keys from an unexpected discovery document, turn a sound protocol into a weak one. Controls. Validate every ID token against the issuer published in discovery metadata, check that the audience is your own client ID, bind the nonce to the browser session and use PKCE on the authorisation code flow. Decision criteria. Use OIDC whenever an application must know who the person or workload is, not just what it may call.
Worked scenario. A partner portal lets staff from several customers sign in with their own identity provider and then call your APIs. OIDC establishes who each person is and which provider vouched for them, while separate audience-restricted access tokens authorise the API calls. Mixing the two, by sending the ID token to the API, is the error to design out.
Detection and measurement
- Log at the provider: authorisation requests with and without
openid, grant type, client ID, PKCE method, redirect URI and requested scopes. - Log at the client: ID token validation failures by reason (issuer, audience, signature, expiry, nonce). A spike in audience or nonce failures can indicate token injection attempts.
- Log at the API: access tokens rejected for wrong audience or insufficient scope, and any request carrying an ID token as a bearer token.
Metrics that show the model is working:
- Percentage of interactive sign-in clients using OIDC code flow with PKCE (target 100%).
- Number of client registrations still allowing implicit or password grants (target 0).
- Percentage of applications keying accounts on
iss+subrather than email. - APIs that accept ID tokens as bearer credentials (target 0).
- Percentage of access tokens that are audience-restricted and, for high-risk APIs, sender-constrained.
Common failure patterns
- Access token as login: the client treats any valid access token or profile API response as proof of the user, so a token phished through another application logs the attacker in (RFC 6749 section 10.16).
- ID token sent to APIs: APIs accept ID tokens as bearer tokens, ignoring that their audience is the client, which lets any relying party's ID token unlock the API.
- Skipped nonce or audience check: the client verifies only the signature, so an ID token issued to another client of the same provider is accepted.
- Accounts linked by email: a multi-tenant or social provider that lets users assert an unverified or recycled email gives an attacker another person's account.
- Implicit flow still enabled: legacy single-page apps keep receiving tokens in the URL fragment long after the code flow is available, contrary to RFC 9700.
- Over-broad scopes: clients request
profile email offline_accessand every API scope by default, so one stolen refresh token reaches far more than the feature needed. See the OAuth Scope Design Guide.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | IA-2 Identification and Authentication (Organizational Users); IA-8 Identification and Authentication (Non-organizational Users) | OIDC is the mechanism that carries user authentication to applications |
| NIST SP 800-53 Rev. 5 | IA-9 Service Identification and Authentication | OAuth client authentication and machine clients |
| NIST SP 800-53 Rev. 5 | AC-3 Access Enforcement; SC-23 Session Authenticity | Scope and audience enforcement at APIs; nonce and session binding at clients |
| NIST CSF 2.0 | PR.AA-03 Users, services, and hardware are authenticated | Covers both user login (OIDC) and client authentication (OAuth) |
| MITRE ATT&CK | T1528 Steal Application Access Token; T1550.001 Use Alternate Authentication Material: Application Access Token | Theft and replay of OAuth tokens, the threat audience and sender-constraint checks address |
| OWASP API Security Top 10 2023 | API2:2023 Broken Authentication | APIs that accept the wrong token type or skip audience checks |
Practitioner checklist
- Use OIDC for every application that signs people in; use OAuth alone only for delegated API access and machine clients.
- Use the authorisation code flow with PKCE
S256for all clients; disable implicit and password grants. - Validate ID tokens fully at the client: issuer, audience, signature, algorithm allow-list, expiry and nonce.
- Key local accounts on
iss+sub; never link accounts on an unverified email claim. - Never send ID tokens to APIs; APIs accept only access tokens issued for their audience.
- Pin the expected issuer and use exact redirect URI matching; add mix-up defence for multi-provider clients.
- Request minimum scopes and claims; keep tokens out of logs and browser storage.
- Authenticate confidential clients with
private_key_jwtor mTLS and sender-constrain high-value access tokens. - Alert on ID token validation failures and on APIs receiving ID tokens.
Standards and references
- OpenID Foundation: OpenID Connect Core 1.0
- IETF: RFC 6749 The OAuth 2.0 Authorization Framework
- IETF: RFC 9700 Best Current Practice for OAuth 2.0 Security
- OpenID Foundation: OpenID Connect Discovery 1.0
- IETF: RFC 8414 OAuth 2.0 Authorization Server Metadata
- IETF: RFC 7636 Proof Key for Code Exchange by OAuth Public Clients
- IETF: RFC 9068 JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens
Related NHI Mgmt Group resources: OAuth 2.0 and OpenID Connect Guide · SAML vs OIDC Guide · Top 10 OAuth Security Risks · API Keys vs OAuth Tokens Guide