Skip to content

Implicit flow

This page helps you keep an old browser app that uses the implicit flow working, and move it off.

The implicit flow skips the authorization code. The browser comes back from the auth server with the tokens themselves, after the # in your redirect URI, and your app’s JavaScript reads them from there. It was made for browser apps from before they could call a token endpoint on another origin. They all can now, so the only reason left to use it is an app you can’t change yet.

Turn it on only for an app that speaks nothing but the implicit flow and that you can’t update yet, and only for that app’s client, while you move it off. Leave the global setting off, so no other client gets it by accident.

  1. In the admin console, open Admin, Clients and click Manage beside your app’s client.

  2. On OAuth2 flows, under Legacy flows (deprecated in OAuth 2.1), set Implicit flow to Enabled, and click Save.

  3. On Redirect URIs, add the page that receives the tokens, such as https://app.example.com/callback, and click Save.

  4. Make a new random state and nonce, keep them, and send the browser to the authorization endpoint with response_type=id_token token:

    GET /auth/authorize?client_id=legacy-spa&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&response_type=id_token%20token&scope=openid%20email&state=af0ifjsldkj&nonce=n-0S6_WzA2Mj HTTP/1.1
    Host: auth.example.com
  5. The user signs in, and the browser comes back with the tokens after the #:

    HTTP/1.1 302 Found
    Location: https://app.example.com/callback#access_token=eyJhbGciOiJSUzI1NiIs...&token_type=Bearer&expires_in=300&id_token=eyJhbGciOiJSUzI1NiIs...&scope=openid+email&state=af0ifjsldkj
  6. Check that state is the one you kept. Check the ID token as a web app does, nonce included, and check its at_hash against the access token. Then replace the address in the browser’s history with one that has no # part, so the tokens don’t stay there.

Implicit flow on the OAuth2 flows tab has three choices: Enabled, Disabled, or inherit the global Implicit flow enabled under Admin, General, which is off in a new install.

response_type What comes back
id_token token An ID token and an access token. The ID token carries at_hash, a hash of the access token.
id_token An ID token only
token An access token only

token id_token works too, since the order doesn’t matter. A request asking for an ID token needs openid in its scope and a nonce, as OpenID Connect Core 1.0 section 3.2.2.1 requires, or it’s refused with invalid_request.

Ask for id_token token when your app needs an access token. at_hash ties the access token to an ID token that carries your nonce, which is the one way your app can tell the access token was issued for its own request. With id_token alone there’s no access token to call UserInfo with, so the user’s claims reach your app only when Include OpenID Connect claims in the ID token is on. It’s on in a new install. See Scopes.

The browser comes back to your redirect URI with these parameters after the #:

Parameter When
access_token response_type has token
token_type With access_token. It’s Bearer.
expires_in With access_token: its lifetime in seconds, the client’s token expiration or else the global one, 300 in a new install
id_token response_type has id_token
scope The scopes granted, which can be fewer than your app asked for
state When your app sent one, unchanged

There’s never a refresh token: RFC 6749 section 4.2.2 forbids one, so offline_access is dropped from the scope, as OpenID Connect Core 1.0 section 11 asks. When the access token expires, send the browser back to the auth server. A user whose session is still valid isn’t asked for their password again.

With response_mode=form_post, the same parameters are posted to your redirect URI by a page that sends a form on its own. response_mode=query is refused with invalid_request, since tokens never travel in the query. Errors come back the same way as tokens, after the # or posted: error, error_description, and state when your app sent one.

The implicit flow goes through the same authorization endpoint as the authorization code flow, with the same checks, sign-in, ACR levels, consent and prompt. These rules are its own:

  • The client needs the implicit flow on, or the request is refused with unauthorized_client. It doesn’t need the authorization code flow on.
  • The redirect URI matches exactly. A loopback redirect URI’s port isn’t ignored, as it is for the authorization code flow, since tokens rather than a code would go to whatever program is listening there.
  • PKCE doesn’t apply. There’s no code to tie it to, and a code_challenge is ignored.
  • The setting is read again just before the tokens are issued, so a sign-in under way when an administrator turns the flow off ends with unauthorized_client and no tokens.
  • The tokens are tied to the user’s session, which must still be valid when they’re issued. Each issue leaves a token_issued_implicit_response entry in the audit log.
  1. On the client’s Authentication tab, choose Public client if it isn’t one, click Save, and confirm with Yes. Your app runs in the browser, so it can’t keep a secret.

  2. On OAuth2 flows, turn on Authorization code with PKCE, and click Save.

  3. On Web origins, add your app’s origin, such as https://app.example.com, so the browser lets it call the token endpoint.

  4. Change your app to send response_type=code with a PKCE challenge, and redeem the code at the token endpoint. Add sign-in to a SPA or mobile app walks through it. Your app gets refresh tokens now too.

  5. Once no user is on the old version, set Implicit flow to Disabled.