Legacy Grants: Implicit and Password

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.

This page documents two grant types for one reason only: to explain, precisely, why neither should be used today. Both the implicit grant and the resource owner password credentials (ROPC) grant were part of RFC 6749; both are already discouraged by RFC 9700 (the OAuth 2.0 Security Best Current Practice); and both are removed outright in the in-progress OAuth 2.1 consolidation (draft-ietf-oauth-v2-1-16, 3 September 2026). Nothing on this page should be read as presenting either grant as a current option — every mention below is historical or diagnostic ("if you inherit a client still doing this, here is what to change and why").

Flows Overview lists both grants with a status column marked "removed in OAuth 2.1"; this page is where that status is justified in full.

The implicit grant (response_type=token)

How it worked

The implicit grant (RFC 6749 §4.2) was designed for browser-based JavaScript applications at a time when PKCE did not yet exist and there was no way for a public client with no backend to complete a code exchange. Instead of returning an authorization code that a backend would trade for a token, the authorization server issued the access token directly, in the front channel, appended to the redirect URI as a URL fragment:

GET /authorize?response_type=token
    &client_id=spa-client
    &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
    &scope=orders.read
    &state=xyz123 HTTP/1.1
Host: as.example.com
HTTP/1.1 302 Found
Location: https://app.example.com/callback#access_token=eyJhbGciOiJSUzI1NiJ9...
    &token_type=Bearer
    &expires_in=3600
    &scope=orders.read
    &state=xyz123

The client-side JavaScript read window.location.hash, parsed out access_token, and used it directly — no second, back-channel request to /token was ever made.

Exactly why it is gone

Every one of the following is a direct consequence of putting the access token in the URL fragment of a front-channel redirect, and every one of them is fixed by not doing that:

  • No client authentication. The implicit grant has no token request, so there is nowhere for a client secret or any other credential to be presented. Anyone who can trigger the redirect gets a token; nothing ties the issuance to a verified client.

  • The token ends up in the URL fragment, and fragments end up in more places than developers expect. Although fragments are not supposed to be sent to the server in an HTTP request line, they are routinely captured anyway by browser history, by any JavaScript library that reads location.href for its own purposes (analytics scripts, error trackers, ad-tech snippets — all running in the same origin with the same access to the DOM), and by the Referer header if the page ever navigates onward carrying the fragment forward through a redirect chain that does not strip it. A token that leaks into browser history or a third-party script’s telemetry is a token an attacker did not need to intercept the network to steal.

  • No refresh token. RFC 6749 §4.2 explicitly withholds refresh tokens from the implicit grant, because issuing a long-lived credential into the same exposed front channel would only compound the exposure above. The practical result was short-lived access tokens with no good renewal story, pushing implementations toward either constant re-authentication or (worse) inventing their own non-standard long-lived-token workarounds.

  • Trivially exfiltrated by any XSS. Because the token lands directly in JavaScript-reachable state (location.hash, and from there almost always into a JS variable or localStorage), any cross-site scripting vulnerability anywhere on the page’s origin can read it and ship it to an attacker-controlled endpoint. There is no HttpOnly equivalent for a token an implicit-grant client is handed this way — the entire design requires the token to be readable by the client’s own script, which is indistinguishable from being readable by an attacker’s injected script.

  • No protection against authorization-response injection. Because the response is a bare redirect with no proof tying it back to a specific request the client itself initiated (the state parameter is optional and historically often skipped), an attacker who can get their own authorization response delivered to the victim’s callback URL can splice in a token or code of their choosing, binding the victim’s session to an attacker-controlled resource. PKCE-less authorization code had the same weakness; PKCE closes it for the code flow, but implicit has no code step for PKCE to protect in the first place.

Replacement

Authorization code + PKCE, run either through a backend-for-frontend as described on Browser-Based Apps (SPAs), or, for a pure browser-only SPA, the browser-only variant on that same page with refresh-token rotation and sender-constraining. There is no legitimate architecture left for which the implicit grant is the right tool — every case it used to serve is served better, and more safely, by authorization code with PKCE.

Resource owner password credentials (ROPC)

How it worked

ROPC (RFC 6749 §4.3) let the client collect the user’s username and password directly — typically in its own native login form — and exchange them for a token in a single back-channel request, with no browser redirect at all:

POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic bmF0aXZlLWFwcDpzZWNyZXQ=

grant_type=password
&username=alice
&password=hunter2
&scope=orders.read
{
  "access_token": "eyJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "8xLOxBtZp8"
}

It was attractive precisely because it looked simple: no redirect, no browser, no fragment parsing — just a form post that reused whatever login UI the client already had.

Exactly why it is gone

The problems below are not implementation bugs that a careful ROPC deployment could avoid — they are the grant’s design, and they are exactly the reasons this whole OAuth Reference section keeps coming back to "the client must never see the user’s password":

  • The client handles the user’s password, defeating the entire point of OAuth. OAuth exists so that a client can act on a user’s behalf without the user handing that client their credentials. ROPC hands the credentials over anyway, in full, to exactly the party OAuth was invented to keep them away from. Every other point below is a specific consequence of this one.

  • No MFA or step-up. The token endpoint accepts a username and password over a single synchronous request; there is no protocol slot in the password grant for "now show a TOTP prompt", "now push an approval to the user’s phone", or "now require a passkey ceremony". Retrofitting any of that means the client itself has to become a multi-step, protocol-aware authentication UI — which is precisely the job an authorization server’s own login page is built for, and precisely what a real browser hands back to the authorization server, as covered in the payoff section of Native and Mobile Apps.

  • No CAPTCHA or risk engine. Bot and credential-stuffing defences — CAPTCHAs, device-risk scoring, IP reputation, velocity checks — live in front of an authorization server’s own login page. A client posting a raw username/password pair to a token endpoint bypasses all of it; the token endpoint has no browser context to run a CAPTCHA challenge in even if it wanted to.

  • No federation or social login. "Sign in with Google" has no password for the client to collect in the first place — the user’s credential lives entirely with the federated identity provider. ROPC has nothing to offer a user whose account is federated; it structurally assumes a local, client-visible password exists.

  • No consent. The authorization-code flow’s redirect to the authorization server is also where a user is shown, and can decline, exactly what a client is asking to access. ROPC has no such screen — the user typed a password into what they believed was a login form and has no visibility into what scopes were actually granted underneath it.

  • No passwordless. Every passwordless primary method documented on Authentication Methods: 2FA and Passwordless (e-mail OTP, magic links, SMS OTP, push approval, passkeys, device-bound credentials) has no password to submit through a password-grant form field. ROPC is structurally incompatible with a passwordless account.

  • Unusable for any account that has no password. This is the sharpest form of the previous three points combined: an account that only ever authenticates via a federated identity provider, a passkey, or an enterprise SSO session simply has no password value to place in the password parameter. ROPC does not degrade gracefully for such accounts; it does not work for them at all.

Replacement

Authorization code + PKCE, run in a system browser — not the client’s own form. The system-browser requirement is the point: it is what puts the login experience back under the authorization server’s control, which is exactly what unlocks every capability the bullet list above says ROPC cannot have. See the full argument on Native and Mobile Apps and, for the CAPTCHA/MFA/ federation payoff specifically, that page’s section on why the authorization server — not the app — should own login.

Both grants are legacy. Neither is presented anywhere in this documentation section as a current option for a new integration. If you are migrating an existing client off one of them, do it as a straight replacement with authorization code + PKCE — there is no intermediate "safer ROPC" or "safer implicit" configuration to migrate to first.

Side by side: what each legacy grant traded away

Implicit / ROPC Authorization code + PKCE

Client authentication on the token exchange

None (implicit has no token exchange at all; ROPC authenticates the client, not the request, and never the user’s consent to specific scopes)

code_verifier proves the request came from the party that started the flow, closing the gap public clients cannot close with a secret

Where the user’s credential is entered

Implicit: nowhere (no password step in-protocol). ROPC: directly into the client’s own UI

Into the authorization server’s own login page, in a browser it controls

Refresh token issued

No (both)

Yes, with rotation and reuse detection

MFA / step-up / CAPTCHA reachable

No — there is no authorization-server-owned UI in the loop for either grant

Yes — the authorization server’s login page can impose any of these at any time, with no client change

Token exposure surface

Implicit: URL fragment, browser history, Referer, any script on the page. ROPC: a bare password value on the wire and in the client’s own logs if it is careless

Authorization code is single-use and short-lived; PKCE neutralises interception even if the code leaks

This table is the concrete version of the same point made throughout this documentation section: every property lost in the left-hand column is a property that comes from a client, rather than the authorization server, holding the user’s credential or the bearer artefact from the very first step.

Migration notes

Migrating from To

Implicit (response_type=token), SPA

Authorization code + PKCE via a backend-for-frontend, or the browser-only variant —  Browser-Based Apps (SPAs).

Implicit (response_type=token), any other public client

Authorization code + PKCE —  Authorization Code and PKCE.

ROPC, native/mobile app with its own login form

Authorization code + PKCE, opened in a system browser or in-app browser tab, never an embedded WebView —  Native and Mobile Apps.

ROPC, first-party single-page app

Authorization code + PKCE via the BFF pattern, or (narrowly) the first-party path described below.

ROPC, legacy CLI or headless script that a human is present for

Device authorization grant —  Device Authorization Grant.

ROPC, machine-to-machine with no human user at all

client_credentials —  Client Credentials and Client Authentication (there was never a user identity here for ROPC to have been correctly modelling).

Every migration in this table trades a request the client itself could construct (a form post with a password, or a token pasted straight into a redirect fragment) for a browser redirect the client does not control the content of. That loss of control is not a regression — it is the entire security property being restored.

The narrow first-party case: draft-ietf-oauth-first-party-apps

The pressure that made ROPC attractive in the first place has not disappeared: a company’s own first-party mobile app, built by the same team that operates the authorization server, often wants a login screen that looks like it belongs to the app rather than handing the user off to a browser. draft-ietf-oauth-first-party-apps (an active Internet-Draft at the time of writing) is the working group’s attempt to give that narrow case a safe answer, rather than leaving ROPC as the closest thing available.

The draft’s approach preserves the properties ROPC threw away: the authorization server, not the client, still drives the actual authentication ceremony — including MFA, step-up, and CAPTCHA challenges — but does so through an API-driven exchange that a first-party app’s own UI can render natively, instead of forcing a system-browser redirect. The critical distinction from ROPC is trust and scope: this is a mechanism for an app the authorization server operator controls and has explicitly registered as first-party, not a general replacement for third-party clients, and it does not reintroduce "the client collects a raw password with no further protocol involvement from the authorization server". Track the draft’s progress before relying on it in production; until it stabilises, the safe default for a first-party app remains authorization code + PKCE in a system browser or in-app browser tab, exactly as for any other native client.

Auditing for lingering usage

Both grants tend to survive longest in the oldest, least-visited corners of a system — an internal admin tool, a partner integration set up years ago, a mobile app build nobody has re-audited since its first release. A few concrete things to grep for:

  • response_type=token anywhere in an authorization request — the unambiguous implicit-grant signature.

  • grant_type=password in any token request, or a username/password pair present alongside a grant_type parameter at all — the ROPC signature. This includes internal service clients that "temporarily" used ROPC against a shared test account, which have a way of quietly becoming permanent.

  • An authorization server’s own metadata document (.well-known/openid-configuration or .well-known/oauth-authorization-server, per Discovery, Metadata and Client Registration) still advertising token in response_types_supported or password in grant_types_supported — a server-side signal that the deployment has not yet closed the door on either grant, independent of whether any current client actually uses it.

  • Client registrations (RegisteredClient entries, in the Spring Authorization Server model) whose configured authorizationGrantTypes still include either grant — a direct, per-client audit trail of who is (or was) relying on them.

Finding any of the above is not an emergency in itself, but it is a concrete, actionable migration item — work through the table above for the specific client shape involved.

A historical footnote: the 2012 book got this wrong, on purpose, for its time

Getting Started with OAuth 2.0 (Boyd, O’Reilly, February 2012) devotes an entire chapter each to the client-side (implicit) flow and the resource-owner password flow, and recommends both as legitimate options for their respective client shapes — because, writing eight months before RFC 6749 was finalised, that was an accurate description of the draft specification’s own contents at the time. Neither PKCE, nor RFC 9700, nor the OAuth 2.1 consolidation existed yet for the book to have anticipated. It is cited here as a concrete, dateable example of exactly the risk this whole documentation section is careful about: a source that was correct and useful when written can describe practices the ecosystem has since actively reversed. The book remains a consulted reference for historical narrative elsewhere in this section (see the section bibliography), but on this page specifically — the two grants it recommends — current practice has gone the opposite direction from what it describes, and the RFCs win without qualification.