Getting Started with OAuth

This section documents OAuth 2.0 (RFC 6749) as amended by the OAuth 2.0 Security Best Current Practice (RFC 9700 / BCP 240), the OAuth 1.0 Protocol (RFC 5849) for historical context, and OpenID Connect Core 1.0, as published at the IETF Datatracker and the OpenID Foundation specifications — and, on the Spring pages, Spring Boot 4.1.x and Spring Security 7.1.x as published at the Spring Security reference documentation — which are the references these pages are written and verified against.

OAuth 2.1 is still an Internet-Draft (draft-ietf-oauth-v2-1-16, 3 September 2026) and is flagged as such everywhere it appears on these pages. It is a working-group consolidation in progress, not a published standard; nothing here should be read as saying otherwise.

This content was generated with the assistance of AI and should be verified against those official specifications before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

OAuth is a delegated authorization framework: it lets a resource owner grant a third-party application limited, revocable access to a resource they control, without ever handing that application their password. This page introduces the problem OAuth solves, the four roles every OAuth deployment is built from, and one complete authorization-code-with-PKCE round-trip, wire-level, from the first browser redirect to the token response a client actually receives.

The problem: why not just send the password?

Picture a valet parking service. You hand the valet a valet key — a special key that starts the engine and opens the door, but cannot open the trunk or the glovebox, and cannot be used to duplicate the full key. The valet gets exactly the capability the job requires, for exactly as long as the job takes, and nothing more. If something goes wrong, you take the valet key back; your house key, which the valet never saw, is untouched.

Before OAuth, letting a third-party application act on a user’s behalf at some other service meant handing over the "full key": the user typed their username and password for the target service directly into the requesting application. A photo-printing site that wanted to fetch a user’s photos from a hosting service asked for the hosting service’s own password. That pattern — sometimes called password anti-pattern — carries a specific, enumerable set of costs, not just a vague sense of insecurity:

  • Misplaced trust. The user must trust the requesting application’s code, its operator, and every dependency in between with a credential that also unlocks everything else the password protects, not just the narrow capability the application actually needs.

  • Phishing desensitisation. Training users to type their real password into any site that asks for it teaches them, structurally, to fall for the next site that asks for it with bad intent.

  • Over-broad access. A password is all-or-nothing. There is no way to hand over "read my contact list" without also handing over "change my password" and "delete my account".

  • Breakage on password change. Every application the user has ever given the password to breaks the moment the user rotates that password for an unrelated reason, and every one of them has to be re-fed the new one.

  • Revocation is all-or-nothing too. The only way to cut off one misbehaving application is to change the password, which cuts off every other application at the same time.

  • Some users have no password to give. A user who signed up through a federated identity provider (Google, Microsoft, an enterprise IdP) may have no password at that service at all — there is nothing to "share".

  • No way to add protection behind the password field. If the actual login only ever happens on the target service’s own page, that service can put a CAPTCHA, a bot-detection check, or a second factor behind the password field and improve it over time. If a third-party application collects the password itself and replays it via an API call, none of that is possible — the application would have to reimplement CAPTCHA and MFA itself, which defeats the purpose of centralising authentication. This point is developed fully on Authentication methods: 2FA and passwordless and is also why native and mobile apps must authenticate in a real browser rather than an embedded WebView.

OAuth replaces "hand over the password" with "hand over a valet key" — a scoped, time-limited, independently revocable access token — issued by the service that owns the resource, after the resource owner has authenticated directly to that service and explicitly consented to the specific access requested.

OAuth is an authorization framework, not an authentication protocol. It answers "can this application read my photos?", not "who is this user?". Authentication — and the layer built on OAuth that does answer "who is this user?" — is OpenID Connect, covered separately. Conflating the two is the single most common OAuth mistake; see ID tokens vs. access tokens.

The four roles

Every OAuth exchange, however it is wired up, is built from exactly four roles, defined in RFC 6749 §1.1:

Role Responsibility

Resource owner

The entity — almost always a person — capable of granting access to a protected resource. The "user" in everyday language.

Client

The application requesting access to the protected resource on behalf of the resource owner (or, for machine-to-machine access, on its own behalf). "Client" here is an OAuth role, not a synonym for "browser" or "frontend" — a server-side web application is a client too.

Authorization server

The server that authenticates the resource owner, obtains their consent, and issues access tokens (and, where applicable, refresh tokens) to the client. Owns /authorize and /token.

Resource server

The server hosting the protected resources; accepts and validates access tokens to decide whether to serve a request. Usually an API.

These are logical roles, not physical servers. In a very common real-world topology the authorization server and the resource server are operated by the same organisation, run in the same cluster, and sometimes even share a process — a Spring Boot service can be both at once (see Spring Boot OAuth integration overview). The roles still matter even when the boxes merge: a JWT-format access token still needs a well-defined issuer and audience, and revocation still needs a token store the resource server can consult, exactly as if the two were separate services. The figure below shows the four roles and the trust boundary each one owns.

The four OAuth roles — resource owner

These four terms are used precisely throughout this reference and are worth fixing before going further:

  • Authentication — proving who you are, to the party you are proving it to. "I am alice@example.com, here is my password / passkey / OTP."

  • Authorization — deciding what an already-identified party is allowed to do. "alice@example.com may read her own photos."

  • Federated authentication — letting a different party vouch for authentication, so the relying party never collects the credential itself: "Google says this is alice@example.com." This is what OpenID Connect and social login provide.

  • Delegated authorization — what OAuth itself provides: the resource owner authorizes a client to act with a limited, revocable capability, without the client ever seeing the resource owner’s credential.

A single login button can trigger all four at once — "Sign in with Google" federates authentication and hands the client an access token for delegated, scoped access to (say) the user’s Google Drive files — which is precisely why the two halves are easy to blur and important to keep separate in your own mental model and your code.

Confidential vs. public clients

RFC 6749 §2.1 splits clients into two types by whether they can keep a secret:

  • A confidential client runs on a server the resource owner and attackers cannot access — a traditional server-side web application, a backend service. It can be issued a client_secret (or a private key) and authenticate itself when it calls /token.

  • A public client runs somewhere the resource owner (or anyone with access to that device or that browser tab) can extract any embedded secret — a single-page application running entirely in the browser, a native mobile or desktop app, a CLI tool. It cannot reliably keep a client_secret, so it authenticates with nothing plus PKCE rather than a secret it cannot actually protect.

This distinction drives almost every difference between OAuth pages in this reference: which grant applies (Flows overview), how the client authenticates (Client credentials and client authentication), and how tokens must be stored and constrained (Native and mobile apps, Browser-based apps).

Client registration

Before any of this can happen, the client must be registered with the authorization server: given a client_id (and, for confidential clients, a client_secret or another authentication mechanism), a set of allowed redirect_uri values, and a set of scopes it may request. Registration exists so the authorization server can show the resource owner who is asking ("Example Photo Printer wants to access your photos") and so it can reject authorization responses aimed at a redirect URI the client never registered — a core defence against authorization-code injection, covered in depth on Security best practices. Registration can be a manual, one-time administrative step, or fully automated via RFC 7591 Dynamic Client Registration — see Discovery, metadata and client registration.

The endpoints

An OAuth authorization server exposes, at minimum, two HTTP endpoints, plus an optional but increasingly universal third one:

Endpoint Purpose

/authorize

A browser-facing endpoint (RFC 6749 §3.1). The client redirects the resource owner’s browser here; the authorization server authenticates the owner, shows a consent screen, and redirects back to the client with an authorization code (or an error).

/token

A back-channel, server-to-server endpoint (RFC 6749 §3.2). The client exchanges an authorization code (or another grant) for an access token, calling this endpoint directly over HTTPS — the resource owner’s browser is never involved in this step.

/.well-known/oauth-authorization-server

The RFC 8414 metadata document: a single JSON file listing every endpoint URL, every supported grant, scope, and signing algorithm, so a client can be configured with just an issuer URL. Covered in full on Discovery, metadata and client registration.

One complete round-trip: authorization code with PKCE

The rest of this page walks a single, complete exchange end to end: a confidential server-side web client signing a user in and obtaining an access token for an API, using the authorization code grant hardened with PKCE (Proof Key for Code Exchange, RFC 7636). This is the grant almost every interactive OAuth flow in this reference builds on; the full parameter-by-parameter reference lives on Authorization code and PKCE — this page shows one concrete instance of it so every later page can assume the reader has seen the whole shape once.

sequenceDiagram participant U as Resource owner (browser) participant C as Client (server-side app) participant AS as Authorization server participant RS as Resource server C->>C: generate code_verifier, derive code_challenge = BASE64URL(SHA256(code_verifier)) C->>U: redirect to /authorize with client_id, redirect_uri, scope, state, code_challenge U->>AS: GET /authorize (the redirect above) AS->>U: authenticate resource owner, show consent screen U->>AS: approve consent AS->>U: 302 redirect to redirect_uri with code, state, iss U->>C: browser follows the redirect, code arrives at redirect_uri C->>AS: POST /token (grant_type=authorization_code, code, redirect_uri,\ncode_verifier, client authentication) AS->>AS: verify code_verifier against the stored code_challenge AS->>C: 200 OK, JSON token response (access_token, refresh_token, expires_in) C->>RS: GET /resource, Authorization: Bearer access_token RS->>C: 200 OK, protected data

Step 1 — the client prepares PKCE and redirects the browser

Before redirecting anywhere, the client generates a random code_verifier and derives a code_challenge from it. The relationship is fixed by RFC 7636 §4.2: code_challenge = BASE64URL(SHA256(code_verifier)). The values below are illustrative — a real code_verifier is a cryptographically random 43-128 character string, and the code_challenge shown is a genuine S256 derivation consistent with it.

GET /authorize?response_type=code
    &client_id=photo-printer-web
    &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
    &scope=photos.read
    &state=af0ifjsldkj
    &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
    &code_challenge_method=S256 HTTP/1.1
Host: authorization-server.example.com
Parameter Meaning

response_type=code

Requests the authorization-code grant (RFC 6749 §4.1.1).

client_id

The client’s identifier from registration.

redirect_uri

Must exactly match one of the client’s registered redirect URIs — byte-for-byte, no partial matching. See Security best practices.

scope

The delegated capability being requested, space-separated if there is more than one value.

state

An opaque value the client generates and later checks on the way back, to bind the response to the request that started it and defeat cross-site request forgery against the redirect endpoint.

code_challenge / code_challenge_method

The PKCE challenge derived from the code_verifier the client is holding, and the method used to derive it (S256 — plain exists but should never be used when S256 is available).

The resource owner’s browser lands on /authorize. The authorization server authenticates the resource owner (however it chooses — password, passkey, federated login, with or without a second factor; see Authentication methods), shows a consent screen naming the client and the requested scope, and, on approval, issues a short-lived, single-use authorization code bound to the code_challenge from Step 1.

Step 3 — the redirect back to the client

HTTP/1.1 302 Found
Location: https://app.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA
    &state=af0ifjsldkj
    &iss=https%3A%2F%2Fauthorization-server.example.com

code is the authorization code; state is echoed back unchanged so the client can verify it matches the value it generated in Step 1. iss (RFC 9207) identifies which authorization server issued this response — its purpose is to defeat mix-up attacks where a client talks to multiple authorization servers and could otherwise be tricked into sending a code meant for one issuer to another; see Security best practices for the attack in full.

Step 4 — the client exchanges the code for tokens

This call happens server-to-server, over the back channel, never through the resource owner’s browser:

POST /token HTTP/1.1
Host: authorization-server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic cGhvdG8tcHJpbnRlci13ZWI6c3VwZXItc2VjcmV0

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&client_id=photo-printer-web
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

The Authorization: Basic header carries client_secret_basic authentication — base64 of client_id:client_secret — one of several client-authentication methods compared on Client credentials and client authentication. The authorization server:

  1. looks up the authorization code and confirms it has not already been used and has not expired;

  2. confirms redirect_uri matches the one used in Step 1;

  3. authenticates the client (here, via the Authorization header);

  4. computes BASE64URL(SHA256(code_verifier)) from the presented code_verifier and confirms it equals the code_challenge stored against this code back in Step 1 — this is the whole of PKCE’s defence: even an attacker who intercepted the authorization code in Step 3 cannot redeem it without the code_verifier, which never left the original client.

Step 5 — the token response

{
  "access_token": "2YotnFZFEjr1zCsicMWpAA",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
  "scope": "photos.read"
}

The client now holds an access token it can present to the resource server. Everything about the shape, lifetime, storage and renewal of these two tokens is covered on Access and refresh tokens; the full parameter set for this grant, every error code, and the state/nonce/PKCE distinctions are on Authorization code and PKCE.

Non-OAuth API authentication, briefly

Not every API needs OAuth. Three simpler mechanisms remain legitimate for service-to-service traffic that has no resource owner and no browser in the loop: a static API key sent as a header or query parameter, which is simple but bearer-like (anyone who has the key can use it, it typically does not expire, and it carries no identity beyond "whoever holds this key"); HMAC request signing, which is conceptually OAuth 1.0’s approach transplanted onto a bespoke API — a shared secret signs each request, so a leaked request is not directly replayable but the signing logic must be reimplemented and kept correct on every client (the whole story of OAuth 1.0, which is exactly why the industry stopped doing this by hand); and mTLS-only authentication, where the client’s TLS certificate itself is the credential and no bearer token exists at all, workable when every client already sits inside a PKI. None of the three offers OAuth’s actual value proposition — delegated, scoped, user-consented, centrally revocable access issued by a party other than the client — so they are the right choice only when there is no resource owner to delegate for in the first place; a machine-to-machine call that does need OAuth’s token model without a user present is the client_credentials grant, covered on Client credentials and client authentication.

Where to go next