Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

OAUTH 2.0 · LAB

Turn on the implicit grant, read the fragment, and turn it off

Allow the implicit grant for one temporary client, receive a token in the URL fragment, see where it ends up and what binds it to nothing, then replace it with the code flow and switch it off.

ReadyUses your lab tenant

The lesson

Builds on: Trust boundaries.

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

Your progress

Press Start before you begin. Only events your tenant records after that count, in the order below. Checking reads your tenant's Audit, so you need Audit read access in it.

  1. Allow the implicit grant in Flow policy

    Recorded as tenant.oauth.policy.update succeeded.

  2. A token arrives in the redirect

    Recorded as oauth.authorize succeeded (tokens_issued) for lab-tmp-editor.

  3. A token in the query string is refused

    Recorded as oauth.authorize rejected (unsupported_response_mode) for lab-tmp-editor.

  4. The same client uses the code flow with PKCE

    Recorded as oauth.token succeeded for lab-tmp-editor.

  5. Switch the implicit grant off again

    Recorded as tenant.oauth.policy.update succeeded.

  6. The implicit request is refused once it is off

    Recorded as oauth.authorize rejected (unauthorized_client) for lab-tmp-editor.

Setup

  1. Press Start on this page.

  2. Open OAuth > Flow policy. Tick Implicit under Allowed grants, token under Allowed authorization responses and fragment under Allowed response modes. The page shows the RFC 9700 warning. Save.

Restore: Break it switches all three off again, and Cleanup confirms it. Nothing else in the tenant uses them.

  1. Create a temporary client from Single-page application named lab-tmp-editor, type Public, with redirect URIs https://beyondthelogin.dev/lab/callback/ and http://127.0.0.1:8765/callback. Also tick the Implicit grant, the token response and the fragment response mode. Tick Enabled and save.

  2. Set ISSUER, EDITOR_ID (the lab-tmp-editor client ID), API_ID and API_SECRET for lab-photo-api, and enc.

Walkthrough

  1. Discovery now advertises the choice: curl -s "$ISSUER/.well-known/openid-configuration" | jq '.grant_types_supported, .response_types_supported' includes implicit and token.

  1. Send the lesson's request. There is no PKCE challenge, because no code will be redeemed.

eval "$(btl-lab state)"
echo "$ISSUER/oauth/authorize?response_type=token&client_id=$EDITOR_ID&redirect_uri=$(enc https://beyondthelogin.dev/lab/callback/)&scope=photos.read&state=$STATE"

Sign in as Ava and approve. The hosted callback page shows the fragment's parameters: access_token, token_type=Bearer, expires_in, scope=photos.read, state and iss. Keep the token as TOKEN.

Why it matters: the token itself came back in the redirect. There was no token request, no client authentication and no PKCE.

  1. Where the fragment goes. Run btl-lab callback --port 8765 in a second terminal, then repeat step 2 with redirect_uri=$(enc http://127.0.0.1:8765/callback). The browser's address bar shows #access_token=..., but the listener prints a request for /callback with nothing after the path.

Why it matters: browsers never send the fragment to the server. Only script running in the page can read it, which is why the hosted callback page could show it and the listener could not.

  1. Open your browser's history. The full address from step 2, token included, is stored there.

Why it matters: a token placed in a URL goes wherever URLs go: history, any script on the callback page, and occasionally a referrer.

  1. Open Audit. Step 2 shows oauth.authorize succeeded tokens_issued for lab-tmp-editor, and no oauth.token event follows it.

  1. Ask for continued access: repeat step 2 with scope=photos.read%20offline_access. The fragment has no refresh token. An implicit client could only send Ava back to the authorization server, as the lesson's hidden iframes did.

  1. Which client was this token issued to? btl-lab introspect "$TOKEN" shows client_id equal to $EDITOR_ID. Introspect a Token Decoder token for Ava as well: a different client_id, and both are valid tokens for Ava. A page reading a fragment cannot introspect, so it has no way to tell a token issued to it from one issued to another client.

Why it matters: nothing in the implicit response binds the token to the client that asked. That is what made access token injection possible.

  1. Tokens never travel in the query string. Add &response_mode=query to step 2's address. The callback carries error=invalid_request, "Responses that carry tokens cannot use the query response_mode. Use fragment or form_post."

  1. The replacement, with the same client. Run eval "$(btl-lab pkce)"; eval "$(btl-lab state)", open a code flow request for $EDITOR_ID with code_challenge=$CHALLENGE&code_challenge_method=S256, approve, and exchange with only the client ID and verifier.

read -r CODE
curl -s -d grant_type=authorization_code -d "code=$CODE" --data-urlencode "redirect_uri=https://beyondthelogin.dev/lab/callback/" \
  -d "code_verifier=$VERIFIER" -d "client_id=$EDITOR_ID" "$ISSUER/oauth/token" | jq '{token_type, scope}'

The token arrives in a response body, never in a URL or history. A real single-page app could make the same call from the browser, because the token endpoint accepts requests from the origins of the client's redirect URIs.

Why it matters: CORS and PKCE removed the reasons for the workaround. RFC 9700 says clients should not use the implicit grant, and RFC 10017 says browser-based clients must not.

Break it

  1. Switch it off for the client: in OAuth > Clients > lab-tmp-editor, untick the Implicit grant, the token response and the fragment mode, and save. Saving revokes the editor's tokens: btl-lab introspect "$TOKEN" answers {"active": false}.

  2. Switch it off for the tenant: in Flow policy, untick Implicit, token and fragment, and save.

  3. Repeat step 2. The callback carries error=unauthorized_client, and discovery no longer lists implicit or token.

Check your work

Press Check my progress. In tenant Audit you can also find tenant.oauth.clients.create and tenant.oauth.clients.update for lab-tmp-editor.

Cleanup

  1. Confirm Flow policy no longer allows Implicit, token or fragment.

  2. Delete lab-tmp-editor.

  3. Clear the callback entries from your browser history.

Back to all labs

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

The Lab