The implicit grant
Imagine the photo editor at editor.example as it would have been built in 2012: a single page of JavaScript with no backend at all. It needed an access token for the photo API, and the obvious way for a page like that to get one was the implicit grant.
Choosing a flow listed it among the flows to avoid. Seeing how it worked, and why it once seemed reasonable, explains why its replacements look the way they do.
How it worked
All domains and tokens in these examples are fictional. The editor sent your browser to the authorization endpoint with response_type=token:
https://auth.photos.example/authorize
?response_type=token
&client_id=photo-editor
&redirect_uri=https%3A%2F%2Feditor.example%2Foauth%2Fcallback
&scope=photos.read
&state=demo-attempt-25
There is no PKCE challenge, because there would be no code to redeem. After you signed in and approved, the authorization server sent the access token straight back in the redirect:
HTTP/1.1 302 Found
Location: https://editor.example/oauth/callback#access_token=demo-implicit-token-25&token_type=Bearer&expires_in=3600&state=demo-attempt-25
The token sits after the #, in the URL's fragment. Browsers do not send the fragment to the server when they load a page. The browser requested /oauth/callback from editor.example without it and received the editor's page. That page's script then read the fragment from the browser's current address, checked state, and kept the token in memory or in browser storage. The specification did not allow a refresh token in this response.
Compare this with the authorization code flow. There is no token request, no client authentication, and no proof that the party receiving the token was the one that asked. The only thing tying the token to the editor is the registered redirect URI.
Why it existed
In 2012, a page's script could not count on being able to send a request to another origin, such as the authorization server's token endpoint, and read the response. Cross-Origin Resource Sharing (CORS), the mechanism Browser applications described, was still being standardized and was not available in every browser people used. A code exchange needs exactly that request. The editor had no backend to make it instead, and as a public client it had no credential to authenticate with anyway.
The implicit grant worked around all three problems. The token arrived through a navigation, which browsers had always allowed across origins, and the fragment kept it out of the editor's own server logs. RFC 6749 presented it as a simplified flow for clients running in a browser, while warning that its convenience should be weighed against its security costs, especially where the authorization code grant was available.
What went wrong
Putting the token in a URL exposed it to everything that handles URLs. The full address, fragment included, went into browser history. Any script on the callback page, including analytics and advertising code, could read it. Browser bugs occasionally leaked fragments through referrer headers. A token meant to pass only from the authorization server to the editor went through several places never designed to hold credentials.
Nothing bound the token to the client that received it. Suppose an attacker obtained a token for your photos, perhaps through a malicious application you once approved. They could start a sign-in at a legitimate application that used the implicit grant, then replace the token in the returning fragment with the stolen one. The application could not tell: the state value was its own, and the token worked at the API. This access token injection was most damaging for applications that treated receiving a valid token as proof that a particular person had signed in, a misuse RFC 6749 itself warned against.
Without refresh tokens, the editor had to send you back to the authorization server whenever its token expired. To avoid interrupting you, applications repeated the authorization request in a hidden iframe and relied on your session cookie at the authorization server to approve it silently. That depended on browsers sending the authorization server's cookies inside a frame on another site, which browsers have since restricted, as Browser applications explained. And because the token was handed over in a redirect, there was no standard way to bind it to a key the client held, so the sender-constrained tokens described in Advanced OAuth could not help.
Current guidance
The reasons for the workaround have gone. Browsers support CORS, so a page's script can call a token endpoint that allows it, and PKCE gives a public client a proof for each attempt without needing a secret. A browser application can run the authorization code flow with PKCE itself, as Browser applications described, or leave tokens to a backend for frontend.
The OAuth 2.0 Security Best Current Practice, RFC 9700, says clients SHOULD NOT use the implicit grant, or any other response type that returns an access token in the authorization response, unless access token injection is prevented and the leakage paths are mitigated. Meeting those conditions takes more machinery than simply using the code flow. For the clients that relied on the implicit grant most, a later best current practice removes even that path. RFC 10017, on browser-based applications, says browser-based clients MUST NOT use the implicit grant and that the authorization server MUST issue their access tokens only from the token endpoint. OAuth 2.1, the working group's consolidation of OAuth 2.0 and its later guidance, leaves response_type=token out entirely.
Existing implicit integrations still run in many places, often in applications nobody has needed to touch for years. Migrating older integrations looks at how to find them and move them without breaking the people who depend on them.