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

Write negative tests for your tenant, then plan the conformance suite

Run your own test plan of requests an honest client never sends, record PASS or FAIL for each module from real tenant answers and Audit reasons, and plan the official FAPI 2.0 test plan.

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. Complete a normal code flow with PKCE

    Recorded as oauth.token succeeded for lab-printer.

  2. Redeem the same code a second time

    Recorded as oauth.token rejected (code_replayed) for lab-printer.

  3. Redeem a code with another attempt's verifier

    Recorded as oauth.token rejected (pkce_failed) for lab-printer.

  4. Send an authorization request without PKCE

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

  5. Redeem a code with a different redirect URI

    Recorded as oauth.token rejected (redirect_uri_mismatch) for lab-printer.

  6. Redeem a code after it expired

    Recorded as oauth.token rejected (code_expired) for lab-printer.

Setup

  1. Use lab-printer and the shell variables from the earlier labs, and start btl-lab callback before each sign-in. Make sure lab-printer requires PKCE and allows openid and photos.read.

  2. Write down the current authorization code lifetime in OAuth > Flow policy, then set it to 60 seconds, the FAPI maximum.

  3. Create a results file with one row per module: module, request, expected, observed, PASS or FAIL.

printf '| Module | Request | Expected | Observed | Result |\n|---|---|---|---|---|\n' > conformance.md

Walkthrough

  1. Complete flow succeeds. Run a normal code flow for lab-printer with scope=openid photos.read and PKCE, and redeem the code.

curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/token" -d grant_type=authorization_code \
  -d code=$CODE --data-urlencode redirect_uri=$REDIRECT -d code_verifier=$VERIFIER | jq -r .access_token

Expect a token, and save it as TOKEN. Record PASS.

Why it matters: "Conformance testing". A positive baseline first, so each negative module has one cause.

  1. Second use of the same code is rejected. Run the same command again with the same CODE. Expect 400 invalid_grant, and Audit oauth.token rejected code_replayed. Then call UserInfo with TOKEN: 401, because a replayed code also revokes the tokens issued from it.

Why it matters: "Why a specification is not enough". This failure would be silent: no honest client ever sends a code twice.

  1. Wrong verifier is rejected. Start two attempts in two shells, approve the second, and redeem its code with the first attempt's verifier. Expect 400 invalid_grant, reason pkce_failed.

  2. Missing PKCE is rejected. Send an authorization request without code_challenge.

curl -s -o /dev/null -w '%{redirect_url}\n' "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$(urlenc $REDIRECT)&scope=openid&state=conf-4"

The redirect carries error=invalid_request; Audit shows oauth.authorize rejected pkce_required.

  1. Redirect URI mismatch is rejected. Get a new code and redeem it with redirect_uri=http://127.0.0.1:8765/other. Expect 400 invalid_grant, reason redirect_uri_mismatch.

  2. Expired code is rejected. Get a new code, wait 70 seconds, then redeem it. Expect 400 invalid_grant, reason code_expired.

  3. Unregistered redirect shows an error page. Open an authorization request with redirect_uri=https://printer.example/pay/callback. The tenant shows its own error page and sends nothing to the listener. Save a screenshot as evidence, the way the suite's REVIEW modules ask a person to.

  4. **iss is present.** Check that every callback in steps 1 to 5 carried iss equal to $ISSUER.

  5. Request objects and request URIs are refused, not ignored. Send request_uri=urn:ietf:params:oauth:request_uri:demo with ordinary parameters, as in Redeem a pushed request URI in the browser, Do today step 2. Expect error=request_uri_not_supported.

  6. Record which FAPI modules you could not run, and why: PAR, sender-constrained tokens and private_key_jwt.

Why it matters: "What testing proves". Your results cover one configuration of one tenant at one moment, and this list says exactly what they do not cover.

Planned walkthrough

Once the tenant has PAR (G11), private_key_jwt (G8) or mutual TLS, DPoP (G14) or certificate binding, a protected resource the suite can call (G3), and FAPI profile mode (G58), run the OpenID Foundation FAPI 2.0 authorization server test plan, hosted or locally:

  1. Register two temporary clients with different keys, lab-tmp-fapi-1 and lab-tmp-fapi-2, so the suite can try mixing them.

  2. Enter their client IDs, keys and the resource address into the suite's configuration, choosing private_key_jwt and DPoP.

  3. Run the plan. Attach your step 7 style screenshots to the REVIEW modules.

  4. Compare the suite's results with your own conformance.md, and note any module the suite runs that your plan missed.

Break it

  1. Weaken the configuration: set lab-printer's PKCE policy to optional and run step 4 again. The request now succeeds, so that module FAILs. Record it.

Restore: set lab-printer's PKCE policy back to required and run step 4 once more. It PASSes again.

This is the lesson's point that a configuration change needs a new test run, ideally an automatic one.

Check your work

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

  • oauth.token succeeded for lab-printer (step 1)

  • oauth.token rejected code_replayed (step 2)

  • oauth.token rejected pkce_failed (step 3)

  • oauth.authorize rejected pkce_required (step 4)

  • oauth.token rejected redirect_uri_mismatch (step 5)

  • oauth.token rejected code_expired (step 6)

conformance.md should list each module with its observed result and Audit reason.

Cleanup

  1. Set the authorization code lifetime back to the value you wrote down.

  2. Revoke any remaining tokens with curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/revoke" -d token=<access_token>.

  3. Keep conformance.md so you can run the plan again after future tenant changes.

Missing infrastructure

  • G11, G8, G14, G3 and G58: the OpenID Foundation FAPI 2.0 test plan needs PAR, private_key_jwt or mutual TLS, DPoP or certificate binding, a protected resource URL the suite can call, and a tenant that enforces the profile. Once they exist, the Planned walkthrough runs as written.

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