Almost every “Login with Google” tutorial uses OAuth 2.0. Almost none of them are doing authentication correctly with OAuth alone. They work because the libraries quietly implement OIDC on top—but developers walk away thinking OAuth 2.0 is an authentication protocol. It isn’t.
This confusion isn’t academic. Teams build login flows on raw OAuth 2.0 access tokens, skip ID token validation, or treat the two protocols as interchangeable. The result is authentication systems that happen to work in development but have real security gaps in production.
OAuth 2.0 Is Not Authentication
OAuth 2.0 is an authorization framework. It answers one question: “Should this application be allowed to access this resource on behalf of this user?” That’s it.
The classic example: you install a photo printing app and it asks to access your Google Photos. You approve. Google gives the printing app an access token scoped to read your photos. The printing app never sees your password, and you can revoke access whenever you want.
Notice what OAuth 2.0 does not tell the printing app:
- Who you are
- Your email address
- Whether you’re actually a real person
- Anything about your identity beyond “someone authorized this access”
An OAuth 2.0 access token is a valet key. It lets the bearer access specific resources. It says nothing about who handed over the key.
The Access Token Problem
Access tokens are opaque to the client application. The spec doesn’t define their format—they could be random strings, JWTs, or database references. The client isn’t supposed to parse them. It passes them to the resource server, which validates them and returns data.
This is by design. Authorization and identity are separate concerns, and OAuth 2.0 intentionally addresses only authorization. But this is exactly why using an access token to determine who logged in is a mistake. The access token wasn’t built to carry identity. Even if you call a /userinfo endpoint with an access token and get back a user profile, you’re relying on a pattern that OAuth 2.0 doesn’t standardize or guarantee.
Enter OpenID Connect
OIDC (OpenID Connect) is an identity layer built on top of OAuth 2.0. It doesn’t replace OAuth—it extends it with the pieces needed for authentication.
The key addition: the ID token.
When you use OIDC, the authorization server returns an ID token alongside the access token. The ID token is a JWT (JSON Web Token) that contains standardized claims about the authenticated user:
sub— a unique, stable identifier for the useremail— the user’s email addressname— display nameiss— who issued the token (the identity provider)aud— who the token is intended for (your application)iat— when the token was issuedexp— when it expiresnonce— ties the token to your specific authentication request
This is what authentication looks like. The ID token is a signed statement from the identity provider saying: “This person is john@example.com, they authenticated with us at this time, and this token is specifically for your application.”
What Else OIDC Adds
Beyond the ID token, OIDC standardizes several things OAuth 2.0 leaves undefined:
The UserInfo endpoint. A standardized API endpoint where your application can retrieve additional claims about the user using an access token. Unlike ad-hoc profile endpoints, this is a defined part of the spec.
Discovery document. A .well-known/openid-configuration URL that tells your application where to find the authorization endpoint, token endpoint, supported scopes, signing keys, and other configuration. No more hardcoding provider-specific URLs.
Standard scopes. openid (required—signals that this is an OIDC request), profile, email, address, phone. These replace the per-provider custom scopes that OAuth 2.0 implementations invented independently.
Session management. Optional specs for handling logout, session state, and back-channel logout notifications.
The Key Difference, Illustrated
OAuth 2.0 alone:
Your app redirects the user to Google. The user logs in and approves access. Google gives your app an access token. Your app can now call Google Drive APIs on the user’s behalf. Your app knows nothing about the user’s identity unless it makes additional API calls—and even then, there’s no standard way to do it securely.
OAuth 2.0 + OIDC:
Your app redirects the user to Google with the openid scope. The user logs in. Google gives your app an access token and an ID token. The ID token is a cryptographically signed JWT that says “this is john@example.com, their unique ID is 12345, and this token was issued for your specific application.” Your app validates the signature, checks the audience and expiration, and now has verified identity.
The difference isn’t subtle. One gives you a key to someone’s data. The other tells you who they are.
ID Tokens vs Access Tokens
These serve fundamentally different purposes, and conflating them is one of the most common security mistakes in authentication implementations.
ID tokens:
- Audience: your application (the client)
- Purpose: prove who the user is
- Format: always a JWT (per the OIDC spec)
- Should be validated by: the client application
- Should be sent to: nowhere else—don’t send ID tokens to APIs
Access tokens:
- Audience: the resource server (the API)
- Purpose: authorize access to resources
- Format: opaque to the client (could be anything)
- Should be validated by: the resource server
- Should be sent to: the API you’re calling
The Common Mistake
Developers often use the access token to identify users. They decode the access token (which might be a JWT), read the sub or email claim, and use that as the user’s identity. This works with many providers, but it’s wrong for several reasons.
First, the access token’s audience is the resource server, not your application. You’re reading a token that wasn’t meant for you. A malicious resource server could replay that access token to impersonate the user at your application.
Second, access token formats aren’t guaranteed. A provider could switch from JWT to opaque tokens tomorrow and your “authentication” breaks.
Third, access tokens don’t include the nonce claim that ties the token to a specific authentication request, making them vulnerable to token substitution attacks.
Use ID tokens for identity. Use access tokens for API access. This isn’t pedantic—it’s the difference between authentication that’s secure by design and authentication that works by accident.
When You Need Which
OAuth 2.0 Alone
Pure authorization scenarios where identity doesn’t matter:
- Service-to-service API access. A backend service needs to call another service’s API. The client credentials grant issues an access token with no user involved.
- Delegated resource access. A third-party app needs to read a user’s calendar or post to their social media. The app needs permission to act, not knowledge of identity.
- Scoped API integrations. CI/CD tools accessing a GitHub repository, analytics tools reading Google Ads data. Authorization scopes define what’s allowed.
OIDC (Which Includes OAuth 2.0)
Any scenario where you need to know who the user is:
- User login. “Sign in with Google/Microsoft/GitHub.” You need a verified identity to create or look up a user account.
- Single sign-on. Users authenticate once and access multiple applications. OIDC’s ID token and session management handle this.
- Identity federation. Trusting an external identity provider’s assertion that a user is who they claim to be.
- User profile access. Retrieving standardized user information (name, email, picture) from an identity provider.
In practice, if users are logging into your application, you need OIDC. If machines are calling APIs, OAuth 2.0 alone is probably sufficient.
Common Mistakes
Using OAuth 2.0 for authentication without OIDC. The most pervasive mistake. Teams implement OAuth 2.0’s authorization code flow, get back an access token, call a proprietary profile endpoint, and call it authentication. This “works” but lacks the security guarantees that OIDC provides—no signed identity assertion, no audience restriction, no nonce to prevent replay attacks.
Sending ID tokens to APIs. The ID token proves identity to your client application. When calling an API, send the access token. If your API needs to know who the user is, it should validate the access token and extract identity from its own trusted source—not accept an ID token from the client.
Skipping token validation. OIDC ID tokens must be validated: check the signature against the provider’s published keys, verify the iss matches the expected provider, confirm the aud matches your client ID, ensure the token isn’t expired, and verify the nonce if you sent one. Skipping any of these steps opens the door to token forgery or substitution.
Confusing scopes with permissions. OAuth 2.0 scopes define what an application can do on behalf of a user. They’re not a substitute for application-level authorization. A user might grant your app the email scope, but that doesn’t mean they should have admin access in your system. Scopes are consent; authorization is your responsibility.
Not using PKCE. Proof Key for Code Exchange should be used in every authorization code flow, not just public clients. It prevents authorization code interception attacks. Most modern libraries enable it by default, but older implementations often skip it.
In Practice, You Rarely Choose One vs the Other
Here’s the thing that confuses people: you almost never implement OAuth 2.0 without OIDC in a user-facing application. Every major identity provider—Google, Microsoft, Okta, Auth0, Keycloak—implements both together. When you add “Sign in with Google,” you’re using OIDC whether you realize it or not (assuming you request the openid scope).
Modern authentication libraries handle both protocols transparently. Libraries like passport.js, next-auth, Spring Security, and oidc-client implement the full OIDC flow. You configure your client ID, client secret, and provider URL. The library handles discovery, token exchange, ID token validation, and session management.
Where understanding the distinction matters:
- Debugging authentication issues. When login breaks, knowing that the ID token carries identity and the access token carries authorization helps you diagnose whether it’s an identity problem or a permissions problem.
- Designing API authentication. Your frontend uses OIDC for login. Your backend APIs should validate access tokens, not ID tokens. Getting this wrong creates subtle security issues.
- Evaluating providers. Some providers have better OIDC implementations than others. Understanding what OIDC should provide helps you evaluate whether a provider’s implementation is complete.
- Security reviews. Auditors and security teams need to know that authentication is based on validated ID tokens from a trusted issuer, not cobbled together from access token claims and profile API calls.
A Minimal Correct Implementation
For most web applications, the correct flow looks like this:
- Redirect user to the identity provider with
scope=openid email profileand a PKCE challenge - Receive the authorization code callback
- Exchange the code for tokens (ID token + access token + refresh token)
- Validate the ID token (signature, issuer, audience, expiration, nonce)
- Extract user identity from the ID token’s claims
- Create or update the user’s session in your application
- Use the access token when calling the provider’s APIs on behalf of the user
Your authentication library almost certainly does steps 2-5 for you. But knowing what it’s doing—and why—means you’ll catch it when something goes wrong.
The Bottom Line
OAuth 2.0 and OIDC aren’t competing protocols. OIDC is a layer on top of OAuth 2.0 that adds the one thing OAuth deliberately left out: identity.
If your application needs to know who a user is, you need OIDC. The ID token is the mechanism, and it comes with security properties (signed, audience-restricted, time-bound) that ad-hoc profile API calls don’t provide. If you’re only authorizing access to resources without caring about identity, OAuth 2.0 alone is fine.
The practical reality is that most developers use both together without thinking about the boundary. That’s fine—until something breaks or a security review asks how you verify user identity. At that point, knowing that your authentication is built on OIDC’s ID token (not an OAuth 2.0 access token you happen to decode) is the difference between a confident answer and an uncomfortable silence.