OPENID CONNECT · LAB
Read a signature end to end and prepare an encryption key
Read your tenant's key set, match each token to its key by kid, verify both kinds of signature, and prepare the encryption key and registration the lesson describes, ready for when the tenant can use them.
Partly readyUses your lab tenant
The lesson
Builds on: Connecting a sign-in to an account.
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.
- G64 Encrypted ID tokens and signed or encrypted UserInfo
- G8 Client authentication beyond `client_secret_basic` and `none`
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.
Sign in to lab-collage
Recorded as
oauth.authorizesucceeded (code_issued) forlab-collage.Receive a signed ID token and a JWT access token
Recorded as
oauth.tokensucceeded forlab-collage.Read the plain UserInfo response you would want protected
Recorded as
oidc.userinfosucceeded (userinfo_served).
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
Signing keys, use and alg in the key set, and signature verification are all real. Registering a client encryption key and receiving encrypted or nested responses is not available yet (G64), so that half is prepared locally and kept off the tenant.
In Lab Photos, open OAuth > Access token managers and confirm the manager assigned to
lab-collageissues thejwtformat, the default.source ~/btl-oidc.sh, runbtl-lab callbackbefore each request, and press Start.
Walkthrough
Read the tenant's key set.
GET$ISSUER/oauth/jwks
Open in console
GET $ISSUER/oauth/jwksAn EC key on P-256 with alg ES256, and an RSA key with alg RS256. Both carry use: "sig".
Why it matters: once a key set holds more than one kind of key, use and alg say what each one is for, so nobody encrypts to a signing key or verifies with the wrong algorithm.
Sign in to
lab-collage, redeem, and match each token to its key by header:
part "$ID_TOKEN" 1
part "$TOKEN" 1
curl -si -H "Authorization: Bearer $TOKEN" "$ISSUER/oidc/userinfo" | grep -i '^content-type'
The ID token names the RS256 kid with typ JWT. The access token names the ES256 kid with typ at+jwt. UserInfo answers plain application/json.
Why it matters: one issuer can sign different messages with different keys and algorithms, and the header tells you which key to select.
Verify both, each with its own rules:
btl-lab verify "$ID_TOKEN" --issuer "$ISSUER" --audience "$CLIENT_ID" --type id --nonce "$NONCE"
btl-lab verify "$TOKEN" --issuer "$ISSUER" --audience "$ISSUER/resource" --type at+jwt
Both pass, and each prints the kid it selected from the key set.
Why it matters: a JWS has three parts, and verification uses the sender's public key. That is the inner layer of every nested response the lesson describes.
Prepare an encryption key the way the lesson's printer does: an RSA key used only for encryption, with its own
kid,useencandalgRSA-OAEP-256. The private half stays on your machine, readable only by you.
node -e 'const c=require("crypto"),fs=require("fs");const {publicKey,privateKey}=c.generateKeyPairSync("rsa",{modulusLength:2048});
fs.writeFileSync("collage-enc-private.pem",privateKey.export({type:"pkcs8",format:"pem"}),{mode:0o600});
console.log(JSON.stringify({...publicKey.export({format:"jwk"}),use:"enc",alg:"RSA-OAEP-256"}))' > collage-enc.jwk
btl-lab thumbprint collage-enc.jwk
Use the printed thumbprint as the key's kid in your notes.
Why it matters: a key limited to one job and one algorithm cannot be misused for another, which the next lesson's failure cases depend on.
Write the registration you would make for
lab-collage, then compare it with what the tenant offers:
{
"userinfo_signed_response_alg": "RS256",
"userinfo_encrypted_response_alg": "RSA-OAEP-256",
"userinfo_encrypted_response_enc": "A256GCM"
}
curl -s "$ISSUER/.well-known/openid-configuration" | jq 'with_entries(select(.key | test("encryption|userinfo_signing")))'
The result is {}: no encryption or UserInfo signing algorithms are listed.
Why it matters: every algorithm you register must be one the provider lists. A provider that does not list it cannot be asked to use it.
Break it
Verify the real ID token with the access token's rules:
btl-lab verify "$ID_TOKEN" --issuer "$ISSUER" --audience "$ISSUER/resource" --type at+jwt
It is rejected: typ is JWT, not at+jwt, and the audience is your client, not the resource. Each kind of token is checked against its own expected type, audience and algorithm. That same discipline later protects the inner JWT of a nested response, where a library that skips the inner check accepts anything.
Check your work
Press Check my progress. The checks look for the sign-in, the token response that gave you both kinds of signed token, and the plain UserInfo response.
Your notes match each kid in the two real tokens to a key set entry with the right use and alg, and hold your own encryption key's thumbprint.
Cleanup
Delete collage-enc-private.pem and collage-enc.jwk, or keep them outside any repository for when G64 exists. Nothing in the tenant changed.
Missing infrastructure
G64 (encrypted ID tokens, signed or encrypted UserInfo). With it, the full lab would publish your encryption key in a client JWKS (or
jwks_uri), register UserInfo as signed then encrypted, receive a five-partapplication/jwtresponse, read its protected header (alg,enc,kid,cty: "JWT"), decrypt it with the private key held locally, verify the inner RS256 JWT with the tenant's key, and checkiss,audandsub.G8 (client authentication beyond
client_secret_basic). The client JWKS that would hold the encryption key would also hold the signing key forprivate_key_jwt.