OAUTH 2.0 · LAB
Read your tenant's metadata member by member and prove supported is not allowed
Read every member of Lab Photos metadata, see a listed grant and an existing scope refused for lab-collage, move an endpoint and follow it, and publish documentation for developers.
ReadyUses your lab tenant
The lesson
Builds on: Discovering server capabilities.
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.
Sign in to start this lab and check your progress. Log in or create an account.
Ask for a scope that exists but is not lab-collage's
Recorded as
oauth.authorizerejected (invalid_scope) forlab-collage.Use a grant the server lists but lab-collage may not
Recorded as
oauth.tokenrejected (unauthorized_client) forlab-collage.Move the revocation endpoint in OAuth > Metadata
Recorded as
tenant.oauth.metadata.updatesucceeded.Revoke a token at the address metadata now gives
Recorded as
oauth.revokesucceeded (access_token_found) forlab-collage.Try to advertise a capability the tenant lacks
Recorded as
tenant.oauth.metadata.updaterejected.
Request console
Requests in this lab can be sent from this page to your tenant: open one and choose Send. Fill in the values below first. They stay in this page's memory and are gone when you leave; secrets are never stored or sent anywhere except the request you send.
Setup
You need collage.sh, servers.json and the shell variables from the issuer labs. Run . ./collage.sh in ~/btl-issuer.
In Lab Photos, open OAuth > Scopes and confirm
photos.read(common) andphotos.delete(exclusive) exist. The Lab Photos preset creates both.Open OAuth > Flow policy and confirm
client_credentialsis allowed. The OAuth core track allows it forlab-print-orders; allow it now if it is not.Confirm
lab-collageallows only theauthorization_codegrant and is not assignedphotos.delete.Press Start on this page with Lab Photos selected.
Walkthrough
List what the document contains and what it leaves out.
GET$ISSUER/.well-known/oauth-authorization-server
Open in console
GET $ISSUER/.well-known/oauth-authorization-servercurl -s "$ISSUER/.well-known/oauth-authorization-server" > meta.json; jq -r 'keys[]' meta.json
jq '{par: .pushed_authorization_request_endpoint, device: .device_authorization_endpoint, registration: .registration_endpoint, status: .btl_endpoint_status}' meta.json
The first three are null: those members are absent. btl_endpoint_status marks the matching paths not_implemented.
Why it matters: a member that would have no value is left out, so a client never builds an address the server did not publish. RFC 8414 requires only a few members; most come from optional extensions.
Read the capability members:
jq '{scopes_supported, response_types_supported, grant_types_supported, code_challenge_methods_supported,
token_endpoint_auth_methods_supported, authorization_response_iss_parameter_supported}' meta.json
scopes_supported lists photos.read and openid but not photos.delete, although that scope exists. Your tenant advertises its common scopes only.
Why it matters: the list shows what the server chooses to advertise. A client uses it to confirm the scopes it needs exist, not as a menu of everything it could ask for.
Supported is not allowed.
photos.deleteexists, but it is notlab-collage's. Start the listener withbtl-lab callback, then:
start_attempt photos "openid photos.delete"
Open the URL. Before any sign-in, the listener receives error=invalid_scope with your state and Lab Photos' iss. Now use a grant that grant_types_supported lists:
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$(srv photos token_endpoint)" -d grant_type=client_credentials | jq .
The result is {"error":"unauthorized_client",...}.
Why it matters: metadata describes the server, not any one client. The client's registration decides which of the listed options it may use, and each request is still decided on its own.
Two meanings of
none.jq '.token_endpoint_auth_methods_supported, .token_endpoint_auth_signing_alg_values_supported' meta.jsonprints a list containingnone, thennull.
Why it matters: here none means a public client that identifies itself without authenticating. The signing algorithm list is required only when a JWT-based client authentication method is offered, and there none would mean an unsigned JWT, which RFC 8414 forbids.
Endpoint addresses are used exactly as given. Get a token for Ava first: start the listener, run
start_attempt photos openid, approve,read_response, then:
check_response && TOKEN=$(redeem "$RESP_CODE" | jq -r .access_token)
In Lab Photos, open OAuth > Metadata and change the revocation endpoint path to /oauth/revoke-v2. Save. Then follow the document:
REVOKE=$(curl -s "$ISSUER/.well-known/oauth-authorization-server" | jq -r .revocation_endpoint); echo "$REVOKE"
curl -s -o /dev/null -w 'old path: %{http_code}\n' -X POST "$ISSUER/oauth/revoke"
curl -s -o /dev/null -w 'published path: %{http_code}\n' -u "$CLIENT_ID:$CLIENT_SECRET" "$REVOKE" --data-urlencode "token=$TOKEN"
The document now gives .../oauth/revoke-v2, the old path returns 404, and the revocation at the published address returns 200.
Restore: set the revocation endpoint path back to /oauth/revoke and save.
Why it matters: nothing requires endpoints to follow a naming pattern. A client that built /oauth/revoke onto the issuer breaks after this change; one that reads metadata follows it, after at most one cache lifetime.
Documentation for people. In OAuth > Metadata, add custom metadata and save:
{"service_documentation": "https://photos.lab.example/developers", "x_lab_owner": "issuer-track"}
Both members now appear in discovery: curl -s "$ISSUER/.well-known/oauth-authorization-server" | jq '{service_documentation, x_lab_owner}'.
Why it matters: service_documentation is where developers read what the document cannot express, such as how to request a scope that needs review. Your tenant accepts informational members and x_ members of its own.
Break it
Try to advertise a capability the tenant lacks. In OAuth > Metadata, add
{"dpop_signing_alg_values_supported": ["ES256"]}to the custom metadata and save. The change is refused and Audit recordstenant.oauth.metadata.updaterejected.
Why it matters: a client would turn on DPoP because the document said so. Runtime capabilities come from what the server really does, never from a typed value.
Try to move the revocation endpoint outside the protocol paths, to
/revoke. That is refused too.
Check your work
Press Check my progress. The checks look in Lab Photos for:
oauth.authorizerejected withinvalid_scope, thenoauth.tokenrejected withunauthorized_client, both forlab-collage.tenant.oauth.metadata.updatesucceeded for the move, thenoauth.revokesucceeded withaccess_token_foundforlab-collage.tenant.oauth.metadata.updaterejected from Break it.
Audit also shows the metadata updates for the restore and the documentation members.
Cleanup
In OAuth > Metadata, confirm the revocation path is /oauth/revoke, and remove the custom members if you do not want them published. Delete meta.json and run unset TOKEN RESP_CODE.