Choosing an OAuth Flow

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.

Every OAuth integration starts with the same question: which grant type does this client use? The answer depends entirely on the client’s shape — can it keep a secret, does a human sit in front of it during login, can it render a browser, can it even display a URL. This page is the entry point for the flow pages that follow: it gives a decision tree keyed on client shape, a table of every grant_type value defined by RFC 6749 and its extensions with its current status, the governing rule that a client must never see the resource owner’s password, and — because this confuses newcomers and experienced engineers alike — a precise separation between an OAuth grant type, an authentication method, and 2FA, which are three different things that this section keeps coming back to.

Decision tree by client shape

Pick the row that matches how the client is deployed, not how it is written. A single-page application compiled to run in a browser is a public client regardless of the backend language it was built with; a batch job running inside a locked-down VPC with a provisioned secret is a confidential client even if it is a five-line script.

Client shape Recommended grant Why

Server-side web application (renders HTML, holds a secret on the server)

Authorization code + PKCE

Confidential client, can authenticate itself at the token endpoint, browser only ever sees the front channel

Single-page application (SPA) running entirely in the browser

Authorization code + PKCE, ideally behind a backend-for-frontend (BFF)

Public client, cannot hold a secret; PKCE replaces client authentication as the proof of origin

Native / mobile app (iOS, Android, desktop)

Authorization code + PKCE via the system browser or an in-app browser tab

Public client; see Native & Mobile Apps for why an embedded WebView is forbidden

Input-constrained device (TV, console, set-top box, IoT with no keyboard)

Device authorization grant

The device cannot render a redirect URI or accept a callback; the user completes login on a second, capable device — see Device Authorization Grant

CLI tool run interactively by a person

Authorization code + PKCE (opens a local browser and a loopback redirect), or the device authorization grant when no local browser is available (SSH session, container shell)

A human is present and can authenticate; the CLI itself must never see the password

Daemon / background service acting as itself (no user)

Client credentials

Machine-to-machine, no resource owner in the loop — see Client Credentials & Client Authentication

Service calling a downstream service on behalf of a user (microservice chain)

Token exchange (RFC 8693) or a JWT bearer assertion grant (RFC 7523)

The upstream identity must be carried or re-asserted, not silently dropped or silently forged — see Token Exchange & Assertion Grants

First-party app owned by the same organization as the authorization server

Authorization code + PKCE, same as any public/confidential client; a narrow first-party exception is still being drafted

Resist the temptation to special-case "our own app" with the resource owner password grant — see draft-ietf-oauth-first-party-apps and Legacy Implicit & Password Grants

flowchart TB start["What kind of client is this?"] --> human{"Is a human\npresent to authenticate?"} human -- "no" --> m2m{"Acting as itself,\nor on behalf of\na previously-authenticated user?"} m2m -- "itself" --> cc["client_credentials\n(Client Credentials)"] m2m -- "on behalf of a user" --> te["token exchange (RFC 8693)\nor JWT bearer assertion (RFC 7523)"] human -- "yes" --> render{"Can the device\nrender a browser\nand receive a redirect?"} render -- "no (TV, console, IoT, headless CLI)" --> dev["device_code\n(Device Authorization Grant)"] render -- "yes" --> secret{"Can the client\nkeep a secret\nconfidentially?"} secret -- "yes (server-side web app)" --> acweb["authorization_code + PKCE\nconfidential client"] secret -- "no (SPA, native app, CLI)" --> acpub["authorization_code + PKCE\npublic client"] acweb --> pkcenote["PKCE required regardless --\nsee authorization-code-and-pkce.adoc"] acpub --> pkcenote

The grant-type table

Every grant type OAuth 2.0 and its extensions define, with its exact grant_type value (where it uses one), the defining specification, the client types it suits, and its status under the in-progress OAuth 2.1 consolidation (draft-ietf-oauth-v2-1-16, 3 September 2026). "Removed in the OAuth 2.1 draft" describes what that Internet-Draft proposes — it is not yet a published standard, so existing deployments of a "legacy" grant are not in violation of anything; they are simply building on a foundation the working group intends to retire.

Grant grant_type value Defining spec Client types Status

Authorization code (+ PKCE)

authorization_code

RFC 6749 §4.1 + RFC 7636 (PKCE)

Confidential and public clients with a redirect-capable user agent

Current — the default choice for any client a user interacts with; PKCE mandatory in OAuth 2.1

Client credentials

client_credentials

RFC 6749 §4.4

Confidential clients, no user

Current — see Client Credentials & Client Authentication

Device authorization grant

urn:ietf:params:oauth:grant-type:device_code

RFC 8628

Input-constrained devices

Current — see Device Authorization Grant

Refresh token

refresh_token

RFC 6749 §6, hardened by RFC 9700 §4.14

Any client already holding a refresh token

Current — rotation and sender-constraining required for public clients under RFC 9700; see Access & Refresh Tokens

Token exchange

urn:ietf:params:oauth:grant-type:token-exchange

RFC 8693

Services relaying or re-scoping a subject’s identity

Current — see Token Exchange & Assertion Grants

JWT bearer assertion

urn:ietf:params:oauth:grant-type:jwt-bearer

RFC 7521 / RFC 7523

Confidential clients presenting a signed assertion instead of interactive login

Current — also usable as a client authentication method, see Client Credentials & Client Authentication

SAML bearer assertion

urn:ietf:params:oauth:grant-type:saml2-bearer

RFC 7521 / RFC 7522

Enterprise clients bridging an existing SAML assertion

Conditional — narrow use, mainly enterprise federation bridges; see Token Exchange & Assertion Grants

CIBA (decoupled authentication)

N/A (backchannel flow, not a token-endpoint grant_type in the same sense)

OpenID Connect CIBA Core 1.0

Devices where the authenticating device and the consuming device differ and no redirect is used at all

Conditional — OIDC-specific, out of scope narrowly on this page; see OpenID Connect

Implicit

token (a response_type, not a token-endpoint grant)

RFC 6749 §4.2

Historically: browser-based apps with no backend

Legacy, removed in the OAuth 2.1 draft — replaced by authorization code + PKCE; see Legacy Implicit & Password Grants

Resource owner password credentials (ROPC)

password

RFC 6749 §4.3

Historically: highly trusted first-party clients

Legacy, removed in the OAuth 2.1 draft — replaced by authorization code + PKCE (or client credentials, for non-interactive callers); see Legacy Implicit & Password Grants

"Removed in the OAuth 2.1 draft" is about the grant type itself, not about any particular authentication method a resource owner might use. The draft removes ROPC because the pattern of a client collecting a password and forwarding it is unsafe regardless of what that password is checked against — it says nothing about passwords, OTPs, or passkeys as authentication methods in general. See the distinction below.

The governing rule: a client never sees the resource owner’s password

Strip away the acronyms and every current grant type in the table above enforces the same rule: the client application never receives the resource owner’s credentials. The client redirects the resource owner to the authorization server (authorization code), presents its own credentials as itself (client credentials), or relays a token issued elsewhere (token exchange, assertion grants) — in no current flow does the resource owner type a password into a screen the client controls.

This is precisely what the two legacy grants got wrong, and precisely why they are gone in the OAuth 2.1 draft:

  • The implicit grant did not involve the password directly, but it handed a bearer access token straight back in a browser redirect fragment with no client authentication and no refresh token — see Legacy Implicit & Password Grants for the full attack surface.

  • The resource owner password credentials grant asked the resource owner to type their password directly into the client’s own form, which the client then forwarded to the token endpoint verbatim. That single design choice defeats the entire point of OAuth — see the same page for the complete list of what it costs (no MFA, no CAPTCHA, no federation, no consent, unusable for passwordless accounts).

The replacement for both, in every client shape that has a redirect-capable user agent, is authorization code
PKCE (Authorization Code and PKCE), because it moves the actual authentication step into the authorization server’s own login page, rendered in a real browser the client does not control. Native & Mobile Apps spells out exactly what that buys a native app (CAPTCHA, brute-force defence, step-up, passkeys, federation, password-manager autofill, all upgradeable without an app release); Browser-Based Apps does the same for the SPA case.

Grant type vs. authentication method vs. 2FA — three different things

This is the distinction the whole OAuth Reference keeps returning to, so it is worth stating precisely once, here, before the flow pages use it as a given.

(a) An OAuth grant type is how a client obtains a token. It is a protocol-level exchange between the client and the authorization server’s token endpoint: which parameters it sends, what it authenticates as, and what comes back. authorization_code, client_credentials, and urn:ietf:params:oauth:grant-type:device_code are grant types. The client cares about the grant type because the client is a party to it.

(b) An authentication method is how the authorization server identifies the resource owner behind that grant. It happens entirely inside the authorization server’s own login ceremony — typically during the authorization code grant’s redirect to /authorize, or during CIBA’s backchannel exchange — and the client never sees it. A password checked against a hash, an e-mail or SMS one-time code, a magic link, a push approval on a trusted device, a WebAuthn/FIDO2 passkey, a device-bound credential, a federated login via "Sign in with Google", and smart-card mutual TLS are all authentication methods. Swapping one for another changes nothing about which grant_type the client used; the client still received an authorization code the same way. This is exactly why OAuth can keep improving how users authenticate without ever touching client code, and it is the subject of its own page: Authentication Methods, 2FA and Passwordless.

(c) 2FA/MFA is a third thing again — an additional security layer stacked on top of whichever primary authentication method is in use. It is not a grant type, and it is not itself a primary authentication method. A resource owner who logs in with a password and then confirms a TOTP code from an authenticator app has completed a hardened password login, not a passwordless one, and not a distinct grant type either — the client still receives its token from the same authorization_code exchange it always would have. Stacking a second factor changes the strength of the authentication event (visible to the client afterwards only as the acr / amr claims and auth_time, if the client bothers to look), never the grant that carried the token.

Layer Lives at

OAuth grant type

The token endpoint; a protocol-level exchange the client is a party to

Authentication method (primary)

The authorization server’s own login ceremony; invisible to the client

2FA / MFA (secondary layer)

Stacked on top of the primary method, inside the same login ceremony; invisible to the client

If you came here looking for "the 2FA flow" or "the passkey flow," there is no such grant type — both are authentication concerns handled entirely by the authorization server. The full enumerated list of primary authentication methods and of 2FA layers, kept in strictly separate lists, lives on Authentication Methods, 2FA and Passwordless; the client-visible request/response mechanics (acr_values, prompt, max_age, acr, amr, auth_time, and RFC 9470 step-up) are covered there too.

Common mistakes when picking a flow

These recur often enough across real integrations to call out explicitly, before the dedicated flow pages go into the mechanics of each one:

  • Choosing the implicit grant for a new SPA because an old tutorial says so. Every current OAuth or OIDC library defaults to authorization code + PKCE for browser-based clients; a tutorial or library version that still recommends response_type=token predates PKCE’s widespread adoption. See Legacy Implicit and Password Grants before reaching for it.

  • Reaching for the resource owner password grant "just for our own first-party app." This is precisely the case the grant’s removal targets — see [The governing rule: a client never sees the resource owner’s password] above, and the narrow, still-drafted exception at draft-ietf-oauth-first-party-apps before assuming this is settled ground.

  • Treating client credentials as "the grant for backend services" without checking whether a user is actually involved. If a backend service is calling a downstream API on behalf of a signed-in user rather than as itself, client credentials silently drops that user’s identity from the call chain — what is needed instead is token exchange or an assertion grant, so the downstream service can still see, and authorize against, who the original request was for.

  • Assuming the device authorization grant is only for TVs. Any client that cannot render a redirect-capable browser qualifies — CLIs run over SSH, kiosks, and IoT devices with a display but no browser engine all fit the same shape; see Device Authorization Grant.

  • Conflating "the client needs the user’s identity" with "the client needs a grant type for 2FA." There is no such grant type — see Grant type vs. authentication method vs. 2FA — three different things below before searching further for one.

Worked examples

A few concrete deployments, to make the decision tree above less abstract:

Deployment Grant Notes

A server-rendered dashboard (Thymeleaf, JSP, or similar) that calls its own backend API

Authorization code + PKCE, confidential client

The server holds a client secret (or a private_key_jwt key pair) and never exposes tokens to the browser beyond a session cookie.

A React SPA calling a public API directly from the browser

Authorization code + PKCE, public client, ideally behind a BFF

See Browser-Based Apps for why "public client, no BFF" is the higher-risk variant of this row.

An iOS app signing users in with the system browser

Authorization code + PKCE via ASWebAuthenticationSession

See Native & Mobile Apps.

A nightly batch job reconciling orders against a partner API, no user involved

Client credentials

See Client Credentials and Client Authentication.

A smart TV app signing a household member in to a streaming service

Device authorization grant

See Device Authorization Grant.

An internal orders service calling an internal inventory service while handling a user’s request

Token exchange (RFC 8693), carrying the caller’s identity forward

See Token Exchange and Assertion Grants.

A CI pipeline job authenticating to a cloud API with no interactive user available

Client credentials with private_key_jwt or workload-identity-backed mTLS

See the client-authentication comparison on Client Credentials and Client Authentication.

Where to go next