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

Narrow your tenant to a written security profile

Turn OAuth's options into a written profile for your tenant, apply every part the tenant can enforce, watch discovery and the runtime follow, and map each FAPI 2.0 attacker to a check.

Partly readyUses your lab tenant

The lesson

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

Partly ready. Most of this lab runs today. Steps that wait on platform features are marked, and Missing infrastructure says what they need.

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. Narrow the Flow policy to your profile's choices

    Recorded as tenant.oauth.policy.update succeeded.

  2. Send a response type the profile removed

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

  3. Make PKCE optional on lab-printer

    Recorded as tenant.oauth.clients.update succeeded.

  4. Redeem a code with no verifier while PKCE is optional

    Recorded as oauth.token succeeded for lab-printer.

  5. Require PKCE on lab-printer again

    Recorded as tenant.oauth.clients.update succeeded.

Setup

  1. Use lab-printer and the shell variables from the PAR labs, and start btl-lab callback before each sign-in.

  2. Write down the current OAuth > Flow policy values, the ID token manager's signing key, and whether the Token Decoder client is enabled, so you can restore them. Other labs rely on refresh tokens and RS256 ID tokens.

Walkthrough

  1. Write your profile as a table with three columns: the choice, the options OAuth allows, and your profile's decision. Cover grants, response types, response modes, PKCE, client types, authorization code lifetime, access token and ID token signing algorithms, refresh token rotation, client authentication, and sender-constrained tokens.

Why it matters: "What a security profile does". The choices are made once, written down, and the same for every client.

  1. Apply what the tenant can enforce:

    • OAuth > Flow policy: grants authorization_code and client_credentials only, response type code only, response modes query and form_post, authorization code lifetime 60 seconds.

    • OAuth > Clients: PKCE required on every client, and the built-in Token Decoder client disabled, because it is a public client.

    • OAuth > ID token managers: point the manager at an ES256 key. Generate one in OAuth > Signing keys first if you have none.

Why it matters: narrowing is allowed. Each setting removes options rather than adding them, which is how ecosystems build on a profile.

  1. Read discovery again and compare it with your table.

curl -s "$ISSUER/.well-known/openid-configuration" | jq '{grant_types_supported, response_types_supported, response_modes_supported, code_challenge_methods_supported, id_token_signing_alg_values_supported, access_token_signing_alg_values_supported}'

Only the profile's choices remain.

Why it matters: a client written to your profile can rely on the metadata, which is the interoperability half of a profile.

  1. Send a request your profile removed: response_type=token for lab-printer.

curl -s -o /dev/null -w '%{redirect_url}\n' "$ISSUER/oauth/authorize?response_type=token&client_id=$CLIENT_ID&redirect_uri=$(urlenc $REDIRECT)&scope=photos.read&state=profile-1"

The redirect carries error=unauthorized_client, and Audit shows oauth.authorize rejected unauthorized_client.

Why it matters: the runtime enforces the narrowing, not just the documentation.

  1. Map the FAPI 2.0 attacker model to checks you can run, and mark each row "protected today" or "needs" a gap:

    • Web attacker writing requests for a registered client: See what a browser-carried authorization request exposes, step 5. Needs G11 for PAR.

    • Web attacker acting as an authorization server: your client compares iss in every response with $ISSUER. Protected today.

    • Network attacker: TLS on every endpoint. Record the protocol from openssl s_client -connect ${ISSUER#https://}:443 </dev/null 2>/dev/null | grep -i protocol.

    • Reader of authorization requests: the request is in browser history, and PKCE keeps a copied request from yielding a usable code. Protected today for codes.

    • Reader of resource requests: a copied bearer token works anywhere, as in Bind the phone app's tokens to a key it generated, Do today step 4. Needs G14 or mutual TLS (G8).

Why it matters: "Starting from an attacker model". The profile is only as complete as the attackers each mechanism answers.

Break it

  1. Weaken one client: on lab-printer, set the PKCE policy to optional and save.

  2. Send an authorization request with no code_challenge, approve as Ava, and redeem the code with no verifier.

curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/token" -d grant_type=authorization_code \
  -d code=<code> --data-urlencode redirect_uri=$REDIRECT | jq '{token_type}'

It works, so your written profile is no longer met. Narrowing that is applied setting by setting can drift one setting at a time.

Restore: set lab-printer's PKCE policy back to required and save.

Check your work

Press Check my progress. The checks look for, in order:

  • tenant.oauth.policy.update succeeded (step 2)

  • oauth.authorize rejected unauthorized_client for lab-printer (step 4)

  • tenant.oauth.clients.update succeeded (PKCE made optional)

  • oauth.token succeeded for lab-printer (the code redeemed with no verifier)

  • tenant.oauth.clients.update succeeded (PKCE required again)

Your step 3 output should match your profile table.

Cleanup

  1. Restore the Flow policy values you saved, including the refresh grant and the original code lifetime.

  2. Re-enable the Token Decoder client.

  3. Point the ID token manager back at its RS256 key if other labs need RS256 ID tokens.

  4. Revoke the access token from Break it: curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/revoke" -d token=<access_token>.

Missing infrastructure

  • G58: a tenant or per-client FAPI profile mode that enforces the whole profile at once, so it cannot drift setting by setting: confidential clients only, PAR required, PKCE S256, code only, 60-second single-use codes, no refresh token rotation, sender-constrained tokens, iss checks, PS256, ES256 or EdDSA only, and the profile's clock rules, advertised in metadata. Today refresh tokens always rotate, and PS256 and EdDSA keys cannot be generated.

  • G11, G14 and G8: PAR, DPoP or mutual TLS sender-constraining, and private_key_jwt, the mechanisms the profile requires that the tenant does not offer yet. Once these and G58 exist, the profile can be switched on instead of assembled by hand.

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