Guide
9 min read

OAuth 2.0 and OpenID Connect explained: flows, grants, and where JWTs fit in

OAuth 2.0 is the foundation of modern API authorization. This guide explains every grant type, walks through the Authorization Code flow step by step, covers PKCE, and clarifies exactly what OpenID Connect adds on top.

JWT Decoder

Inspect the claims inside any access token or id_token

Open tool

What OAuth 2.0 is (and is not)

OAuth 2.0 is an authorization framework, not an authentication protocol. It answers the question "can this application access this resource?" : not "who is this user?" This distinction matters because many developers reach for OAuth when they actually need authentication, and end up building something insecure.

The canonical example: "Sign in with Google" uses OAuth 2.0 underneath, but the authentication part is handled by OpenID Connect, a thin identity layer that sits on top of OAuth 2.0. More on that later.

OAuth 2.0 is defined in RFC 6749. It replaced OAuth 1.0, which required complex request signing that was error-prone to implement.

The four roles

Every OAuth 2.0 interaction involves four actors:

Resource Owner

The user who owns the data and can grant access to it.

Client

Your application that wants to access the resource on behalf of the user.

Authorization Server

The server that authenticates the user and issues tokens (e.g. Google, Auth0, Okta).

Resource Server

The API that holds the data the client wants to access. It trusts tokens issued by the Authorization Server.

The four grant types

OAuth 2.0 defines multiple flows ("grants") for different client types and scenarios. Picking the wrong one is a common source of security bugs.

Authorization Code

Web apps / Mobile

The user is redirected to the Authorization Server, authenticates, and is redirected back with a short-lived code. The client exchanges the code for tokens server-side. The most secure and widely used flow.

Authorization Code + PKCE

SPAs / Native apps

Identical to Authorization Code, but replaces the client secret with a cryptographic challenge (code_verifier + code_challenge). Used for public clients that cannot safely store a secret.

Client Credentials

Machine-to-machine

No user is involved. The client authenticates with its own client ID and secret and receives an access token directly. Used for background services, cron jobs, and microservice communication.

Device Code

Browserless devices

The device displays a code and a URL. The user visits the URL on another device, enters the code, and authenticates. Used for smart TVs, CLI tools, and IoT devices.

Authorization Code flow: step by step

This is the flow you will implement most often. Here is exactly what happens:

  1. 1The client redirects the user to the Authorization Server with response_type=code, client_id, redirect_uri, scope, and a random state value.
  2. 2The user authenticates and grants consent to the requested scopes.
  3. 3The Authorization Server redirects back to redirect_uri with a short-lived authorization code and the state value.
  4. 4The client verifies the state value (CSRF protection), then exchanges the code for tokens by making a back-channel POST request with the code and client secret.
  5. 5The Authorization Server returns an access token and, if offline_access scope was requested, a refresh token.
  6. 6The client sends the access token in the Authorization header on each API request.
  7. 7When the access token expires, the client uses the refresh token to get a new one without user interaction.

PKCE: authorization code for public clients

PKCE (Proof Key for Code Exchange, pronounced "pixie") solves a specific problem: single-page apps and native mobile apps cannot safely store a client secret, because anything stored in the browser or app binary is accessible to an attacker.

Instead of a client secret, PKCE uses a per-request cryptographic challenge:

ParameterWhat it is
code_verifierA random 43-128 character string generated fresh for every authorization request. Never sent to the browser.
code_challengeSHA-256 hash of the code_verifier, Base64URL-encoded. Sent in the authorization request. Even if intercepted, it reveals nothing about the verifier.

When the client exchanges the code for tokens, it sends the original code_verifier. The Authorization Server hashes it and confirms it matches the code_challenge it stored. An attacker who intercepts the authorization code cannot use it without the verifier.

What OpenID Connect adds

OpenID Connect (OIDC) is a thin identity layer on top of OAuth 2.0. It adds one key concept: the id_token. This is a JWT issued alongside the access token that contains claims about the authenticated user.

OIDC also defines a /userinfo endpoint the client can call with an access token to retrieve additional user profile data.

Standard OIDC scopes: openid (required), profile, email. Including openid in the scope is what triggers OIDC and causes an id_token to be returned.

Standard claims in an id_token:

ClaimDescription
subUnique identifier for the user within the issuer.
emailThe user's email address (requires email scope).
nameThe user's full display name (requires profile scope).
pictureURL of the user's profile photo (requires profile scope).
issIssuer: the URL of the Authorization Server that issued the token.
audAudience: the client_id this id_token was issued for. Always validate this.

Where JWTs fit in

OAuth 2.0 does not require any specific token format. But in practice, access tokens are frequently JWTs because they allow Resource Servers to validate them locally without calling the Authorization Server.

id_tokens in OIDC are always JWTs. They contain the user's claims and a signature from the Authorization Server's private key. Your app verifies the signature using the Authorization Server's public key (available at the /.well-known/jwks.json endpoint).

Use the JWT Decoder below to inspect the header, payload, and expiry of any token you receive.

Frequently asked questions

What is the difference between OAuth 2.0 and OpenID Connect?

OAuth 2.0 is an authorization framework: it lets a third-party app access resources on a user's behalf. It does not tell you who the user is. OpenID Connect (OIDC) is an identity layer built on top of OAuth 2.0 that adds authentication. It introduces the id_token (a JWT about the user) and the /userinfo endpoint. Rule of thumb: use OAuth 2.0 to grant access, use OIDC to verify identity.

Why is the implicit flow deprecated?

The implicit flow returned tokens directly in the URL fragment, which exposed them to browser history, referrer headers, and log files. With PKCE now available for all public clients (SPAs, mobile apps), there is no reason to use implicit. The OAuth 2.0 Security Best Current Practice document formally deprecates it.

What is a refresh token and why does it expire differently from an access token?

Access tokens are short-lived (minutes to hours) to limit damage if stolen. Refresh tokens are long-lived (days to months) and are stored more securely. When the access token expires, the client presents the refresh token to the Authorization Server and receives a new access token without user interaction. Refresh tokens are revocable, so they give you a revocation handle that short-lived access tokens lack.

Should I build my own authorization server?

Almost certainly not. Auth0, Okta, Cognito, Keycloak, and similar platforms handle token issuance, key rotation, PKCE, refresh token rotation, session management, and dozens of edge cases that are easy to get wrong. Build one only if you have a very specific compliance or data-residency requirement that hosted solutions cannot meet.

What is the state parameter for?

The state parameter is a random value the client generates and includes in the authorization request. The Authorization Server echoes it back in the redirect. The client verifies it matches the original value before exchanging the code for tokens. This prevents Cross-Site Request Forgery (CSRF) attacks, where a malicious site tricks your app into accepting a code it did not request.

What is token introspection?

Token introspection (RFC 7662) is a way for a Resource Server to ask the Authorization Server "is this token valid?" by sending the token to a dedicated endpoint. It is useful when access tokens are opaque strings rather than JWTs, since opaque tokens cannot be validated locally. JWTs, by contrast, can be verified without a network call using the Authorization Server's public key.

Inspect your tokens

Paste any access token or id_token to instantly see its claims, algorithm, and expiry.

Open JWT Decoder