How OAuth and OpenID Connect work
How OAuth 2.0 lets an app act on your behalf without your password, from the four roles and the authorization code flow to PKCE, access and refresh tokens, JSON Web Tokens, sign-in with OpenID Connect, and today's security best practices.
Many apps want to do something with your data in another service: a printing service that prints photos from your photo library, a calendar app that reads your work calendar, a website that lets you “sign in with” an account you already have. The naive way is to hand the app your password for the other service. RFC 6749, the OAuth 2.0 specification, lists what goes wrong then: the app has to store your password, it gets access to everything in your account, you cannot withdraw access from one app without changing your password for all of them, and if the app is compromised, so is your password.
OAuth 2.0 replaces the password with a token: a credential that grants one app limited access, for a limited time, that you approve and can withdraw. OpenID Connect builds on OAuth to tell an app who you are, so that you can sign in to it with an account you already have. This article explains both, using the examples from their specifications.
Four roles
OAuth describes every interaction with four roles, illustrated in RFC 6749 with a printing service and a photo-sharing service:
- The resource owner is the person whose data it is: you, with your photos.
- The resource server holds the data: the photo-sharing service’s API.
- The client is the app that wants access: the printing service.
- The authorization server asks you whether you agree and issues tokens. It may be the same server as the resource server or a separate one.
The key point is that you type your password only at the authorization server, never in the client. The client receives an access token instead, a string that stands for a specific scope (what it may do, such as “read photos”), a lifetime and other limits.
Access tokens and refresh tokens
Most access tokens are bearer tokens (RFC 6750): “any party in possession of the token (a ‘bearer’) can use the token”, without proving anything else. The client sends it with each request to the resource server:
GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM
Because a stolen bearer token works for whoever holds it, RFC 6750 recommends issuing short-lived bearer tokens, valid for one hour or less; the example in RFC 6749 expires after 3,600 seconds. To get new ones without asking you again, the client can also receive a refresh token, which it exchanges at the authorization server for a fresh access token. The refresh token never goes to the resource server.
The authorization code flow
The most important way to get a token is the authorization code grant. It runs through your browser, and it hands out the token in two steps: first a short-lived code through the browser, then the token directly from server to server. Here it is with the example values from RFC 6749.
1. The client sends you to the authorization server. It redirects your browser to the
authorization endpoint with its identity, where to send you back, and a random state value:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
2. You sign in and decide. The authorization server authenticates you, in whatever way it chooses, and asks whether the client may have the requested access.
3. The server sends you back with a code. If you agree, it redirects your browser to the
client’s registered address, with an authorization code and the same state:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
4. The client exchanges the code for tokens. It sends the code to the token endpoint, directly and not through the browser, and authenticates itself:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
5. The server returns the tokens:
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "example",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA"
}
Why the detour through a code? Everything that passes through the browser, such as URLs, can leak into browser history, logs or other pages. The code alone is not enough: it must expire quickly, RFC 6749 recommends at most 10 minutes, it may be used only once, and the client must authenticate when redeeming it. The valuable tokens themselves never appear in a URL.
Clients that cannot keep secrets: PKCE
RFC 6749 distinguishes two kinds of clients. A confidential client runs on a server and can keep a secret, such as the password it uses in step 4. A public client cannot: an app installed on a phone, or code running in a web page, can be taken apart by anyone, so any secret inside it is not a secret.
For public clients, a stolen code would be as good as a token. PKCE (Proof Key for Code Exchange, pronounced “pixy”, RFC 7636) closes this gap with a one-time secret per login:
- The client generates a random code verifier, 43 to 128 characters long.
- It computes the code challenge: the SHA-256 hash of the verifier, encoded in base64url. This
method is called
S256. - It sends only the challenge in step 1, and the verifier in step 4. The authorization server hashes the verifier and checks that it matches the challenge from step 1.
With the example values from RFC 7636:
code_verifier = dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
code_challenge = BASE64URL(SHA-256(code_verifier))
= E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
An attacker who intercepts the code does not know the verifier, and cannot compute it from the challenge, because a hash cannot be reversed. PKCE was designed for apps on devices, but RFC 9700, the current security best practice for OAuth, requires it for all public clients, recommends it for confidential ones too, and requires every authorization server to support it.
Other ways to get a token
- Client credentials grant (RFC 6749): when a service acts on its own behalf, for example a backend calling another backend, it requests a token with just its own credentials. No person is involved.
- Device authorization grant (RFC 8628): for devices with no browser or no keyboard, such as
smart TVs. The device shows a short code, for example
WDJB-MJHT, and an address such ashttps://example.com/device; you open that address on your phone, type the code and approve, while the device waits for its token. - Retired grants. RFC 6749 also defined an implicit grant, which returned the access token directly in the browser’s URL, and a password grant, in which the client collected your password. RFC 9700 says clients should not use the implicit grant and that the password grant must not be used at all.
OpenID Connect: who is the user?
OAuth answers “what may this app do?”, not “who is this person?”: it does not define how a client learns who the user is. OpenID Connect fills this gap. In its own words, it is “a simple identity layer on top of the OAuth 2.0 protocol”.
A client asks for it by including the scope openid in the authorization request. Together with
the access token, it then receives an ID token: a statement, signed by the authorization server
(in OpenID Connect called the OpenID Provider), about who signed in. It always contains:
| Claim | Meaning |
|---|---|
iss |
Who issued the token: the provider’s address |
sub |
A stable identifier for the user at that provider |
aud |
Whom the token is for: it must contain the client’s ID |
exp |
When the token expires |
iat |
When the token was issued |
The client must check these claims and the signature before trusting the token.
JSON Web Tokens
ID tokens, and often access tokens too, are JSON Web Tokens (JWT, RFC 7519): three parts, each encoded in base64url and separated by dots. Here is the example from RFC 7519:
- The header says how the token is protected, here with HMAC SHA-256 (
HS256). - The payload contains the claims. In the example,
expis 1300819380 seconds after 1 January 1970, which is 22 March 2011, 18:43 UTC. - The signature protects header and payload against changes.
Base64url is an encoding, not encryption: anyone who gets the token can read its claims. Never put
secrets in a signed JWT. And a receiver must decide in advance which algorithms it accepts: RFC 8725
requires libraries to let the caller specify the allowed algorithms and to use no others, instead of
trusting the alg value inside the token.
Security today
OAuth 2.0 was published in 2012. RFC 9700 (January 2025) collects what has been learned since:
- Exact redirect addresses. The authorization server must compare the redirect URI with the registered one by exact string matching, so that codes cannot be sent elsewhere.
- Protection against forged responses. Clients must prevent cross-site request forgery, with
PKCE, the OpenID Connect
nonce, or a one-timestatevalue bound to the user’s browser. - Tokens bound to the client. Sender-constrained tokens, for example with DPoP (RFC 9449), require the client to prove possession of a key with each use, so a stolen token alone is useless. Refresh tokens for public clients must be sender-constrained or rotated with every use.
- Least privilege. Access tokens should allow only what is needed and be restricted to the resource servers they are meant for.
- Real browsers for apps. Native apps must not use embedded web views for the login page, but the system browser (RFC 8252), where the app cannot read what the user types.
- Keep tokens out of web pages. For applications that run in the browser, RFC 10017 (2026) recommends as the most secure option a backend for frontend: a small server that acts as a confidential client and keeps all tokens, while the page only holds a session cookie.
OAuth 2.1, still an Internet-Draft in 2026, merges OAuth 2.0 with these lessons: PKCE becomes part of the authorization code flow, exact redirect matching is required, and the implicit and password grants are removed.
Key points
- OAuth lets an app act on your behalf with a token instead of your password. You approve at the authorization server, and the client uses the token at the resource server.
- The authorization code flow returns a short-lived, single-use code through the browser and the tokens directly from server to server.
- PKCE ties the code to the client that started the login. It is required for public clients and recommended for all.
- Access tokens are short-lived and usually bearer tokens; refresh tokens get new ones.
- OpenID Connect adds an ID token, a signed JWT that says who signed in.
- A JWT can be read by anyone; its signature only protects it against changes.
Sources
- The OAuth 2.0 Authorization Framework (RFC 6749)D. Hardt, RFC Editor, 2012Accessed
- The OAuth 2.0 Authorization Framework: Bearer Token Usage (RFC 6750)M. Jones, D. Hardt, RFC Editor, 2012Accessed
- Proof Key for Code Exchange by OAuth Public Clients (RFC 7636)N. Sakimura, J. Bradley, N. Agarwal, RFC Editor, 2015Accessed
- Best Current Practice for OAuth 2.0 Security (RFC 9700)T. Lodderstedt, J. Bradley, A. Labunets, D. Fett, RFC Editor, 2025Accessed
- OAuth 2.0 for Native Apps (RFC 8252)W. Denniss, J. Bradley, RFC Editor, 2017Accessed
- OAuth 2.0 for Browser-Based Applications (RFC 10017)A. Parecki, P. De Ryck, D. Waite, RFC Editor, 2026Accessed
- OAuth 2.0 Device Authorization Grant (RFC 8628)W. Denniss, J. Bradley, M. Jones, H. Tschofenig, RFC Editor, 2019Accessed
- OAuth 2.0 Demonstrating Proof of Possession (DPoP) (RFC 9449)D. Fett, B. Campbell, J. Bradley, T. Lodderstedt, M. Jones, D. Waite, RFC Editor, 2023Accessed
- JSON Web Token (JWT) (RFC 7519)M. Jones, J. Bradley, N. Sakimura, RFC Editor, 2015Accessed
- JSON Web Token Best Current Practices (RFC 8725)Y. Sheffer, D. Hardt, M. Jones, RFC Editor, 2020Accessed
- OpenID Connect Core 1.0 incorporating errata set 2N. Sakimura, J. Bradley, M. Jones, B. de Medeiros, C. Mortimore, OpenID Foundation, 2023Accessed
- The OAuth 2.1 Authorization Framework (draft-ietf-oauth-v2-1-16)D. Hardt, A. Parecki, T. Lodderstedt, IETF, 2026Accessed