IDENTITY FUNDAMENTALS · LAB
One sign-in, two relying parties, and an account that is not the same
Use your tenant as the identity provider for two applications, see single sign-on without a second password, compare iss and sub across two providers, and disable an account through SCIM.
Partly readyUses both lab tenants
The lesson
Builds on: Establishing an identity, How applications communicate.
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.
- G26 Inbound federation (external OIDC or social IdP) and account linking
- G17 OIDC logout: RP-initiated, front-channel, back-channel, session management
- G25 SAML
- G66 Second lab tenant for every learner: additional tenants need a paid subscription or a BTL grant, so labs that use Lab Mail cannot be completed by an ordinary learner yet
Needs a second tenant. This lab also uses Lab Mail, a second tenant. Additional tenants currently need a paid subscription or a BTL grant, so you may not be able to do the Lab Mail steps yet (gap G66).
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.
Get a code for the printer without a second password
Recorded as
oauth.authorizesucceeded (code_issued) forlab-printerabout[email protected].Sign Ava out at the identity provider
Recorded as
account.sign_outsucceeded (signed_out).See a silent sign-in refused after sign-out
Recorded as
oauth.authorizerejected (login_required) forlab-printer.Disable Cora through provisioning
Recorded as
scim.user.locksucceeded about[email protected].See Cora's existing access token refused
Recorded as
oidc.userinforejected (invalid_token).
Setup
You need Ava and Cora, lab-printer, lab-provisioning and the toolkit from earlier labs. This lab also starts your second tenant.
On this lab page choose Lab Photos and press Start.
Set the variables and helpers from the How applications communicate lab, plus the SCIM base URL:
ISSUER=https://tenant-<id>.beyondthelogin.dev
CLIENT_ID=<lab-printer client ID>
read -rs CLIENT_SECRET
REDIRECT=http://127.0.0.1:8765/callback
SCIM="$ISSUER/scim/v2"
authz() { echo "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$(node -p 'encodeURIComponent(process.argv[1])' "$REDIRECT")&scope=$1&state=$STATE&nonce=$NONCE&code_challenge=$CHALLENGE&code_challenge_method=S256"; }
redeem() { curl -s -u "$CLIENT_ID:$CLIENT_SECRET" -d grant_type=authorization_code --data-urlencode "code=$CODE" --data-urlencode "redirect_uri=$REDIRECT" -d code_verifier="$VERIFIER" "$ISSUER/oauth/token"; }
fresh() { eval "$(btl-lab pkce)"; eval "$(btl-lab state)"; }
Give Cora a password on her record in Lab Photos, and get a provisioning token as in the Establishing an identity lab (
PROV_ID,read -rs PROV_SECRET,SCIM_SCOPE, thenSCIM_TOKEN).Create your second tenant and name it
Lab Mail, or use one you own. In Lab Mail, create the user Ava Archer with[email protected]and set a password. Keep its issuer:
ISSUER2=https://tenant-<other id>.beyondthelogin.dev
Walkthrough
Map the roles. Lab Photos is the employer's identity provider. The Token Decoder plays the training portal, and
lab-printerplays the photo library. Both are relying parties.In a private window, sign in through
$ISSUER/token-decoderas Ava withopenid profile email, using her password and authenticator code. Note the ID token'siss,subandaud, which is the Token Decoder's own client ID. Keep the ID token for Break it:
read -rs DEC_IDT
In the same window, run
fresh; authz openid%20profileand open the URL. No password or code is asked; approve consent if it appears. Redeem the code and read the ID token:
CODE=<code from the callback>
redeem > printer.json
btl-lab decode "$(jq -r .id_token printer.json)"
AT=$(jq -r .access_token printer.json)
Expected: the same iss and sub as in step 2, but aud is lab-printer's client ID.
Why it matters: the provider reused its session and issued a new message for the new application. Neither application saw Ava's password or the other's cookie, and each receives a message addressed to it.
In Audit, the
oauth.authorizeevent forlab-printershowscode_issuedwith nouser_signed_inbefore it.Connect a sign-in to an account. In another private window, sign in through
$ISSUER2/token-decoderas Lab Mail's Ava. Compare the two ID tokens: theemailis the same, whileissandsubboth differ.
Why it matters: the photo library keys Ava's local account on the pair (iss, sub). The same email from another provider says nothing about who controls the Lab Photos account. Linking on that match alone is exactly the danger the lesson describes.
Fresh authentication: in the first window run
freshand open$(authz openid)&max_age=0. You must enter Ava's password and code again, andauth_timein the new ID token changes.
Why it matters: an application can ask for more than the provider's existing session.
Sign Ava out at
$ISSUER/account. Then call UserInfo with the photo library's access token from step 3, and ask for a silent sign-in:
curl -s -H "Authorization: Bearer $AT" "$ISSUER/oidc/userinfo" | jq
fresh; curl -s -o /dev/null -w '%{redirect_url}\n' "$(authz openid)&prompt=none"
Expected: UserInfo still answers with Ava's claims until the token expires, while the silent request comes back with error=login_required.
Why it matters: signing out at the provider ended the provider session only. Each application keeps what it already received. Coordinating logout is a separate part of the design (G17).
Keep account records current. Run
fresh; authz openid, open the URL in a new private window, and sign in as Cora. Keep her access token, then disable her as the HR system would:
CODE=<Cora's code from the callback>
CORA_AT=$(redeem | jq -r .access_token)
CORA_ID=$(curl -s -G "$SCIM/Users" -H "Authorization: Bearer $SCIM_TOKEN" --data-urlencode 'filter=userName eq "[email protected]"' | jq -r '.Resources[0].id')
curl -s -X PATCH "$SCIM/Users/$CORA_ID" -H "Authorization: Bearer $SCIM_TOKEN" -H "Content-Type: application/scim+json" \
-d '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","path":"active","value":false}]}' | jq '{id, active}'
curl -si -H "Authorization: Bearer $CORA_AT" "$ISSUER/oidc/userinfo" | grep -iE '^(HTTP|www-authenticate)'
Expected: "active": false, then 401 with www-authenticate: Bearer error="invalid_token". Audit shows scim.user.lock succeeded for Cora.
Why it matters: the change was enforced without waiting for Cora to sign in again. That is what provisioning adds to federation.
Account linking that exists today: in the Establishing an identity lab,
lab-provisioninglinked a SCIM record to Cora's existing account by email. Compare that with step 5.
Why it matters: the SCIM link was acceptable because an authenticated system of record you configured made it, under a setting you chose. A sign-in from another provider with a matching email has established nothing about control of the existing account.
Planned walkthrough
These steps need inbound federation and account linking (G26).
In Lab Photos, add Lab Mail as an external OpenID provider. Lab Mail's Ava signs in to Lab Photos through it, and the tenant stores (
iss,sub) from Lab Mail on a new local account.Lab Mail's Ava then asks to link to Lab Photos's Ava, whose email matches. The tenant requires the existing account's password and authenticator code, or a confirmation link sent to the verified address already on that account, before it creates the link.
Remove the link and confirm that a Lab Mail sign-in no longer reaches Ava's photos.
With SAML (G25), compare an assertion for the same sign-in with the ID token from step 3. With coordinated logout (G17), step 7 would end the photo library's session too.
Break it
Treat a message meant for another application as yours:
btl-lab verify "$DEC_IDT" --issuer "$ISSUER" --audience "$CLIENT_ID" --type id
Expected: the signature check passes and the audience check fails.
Why it matters: a valid signature from a trusted provider is not enough. The message must also be addressed to this relying party.
Check your work
Press Check my progress. It looks for:
oauth.authorizesucceeded with reasoncode_issuedforlab-printerand Ava.account.sign_outsucceeded with reasonsigned_out.oauth.authorizerejected with reasonlogin_requiredforlab-printer.scim.user.locksucceeded for Cora.oidc.userinforejected with reasoninvalid_token.
Cleanup
Re-enable Cora with the same PATCH and
"value":true. Audit showsscim.user.unlock.Delete
printer.jsonand rununset AT CORA_AT DEC_IDT.Keep Lab Mail and its Ava. Later labs use the second tenant.
Missing infrastructure
G26 Inbound federation and account linking. The tenant cannot accept sign-ins from an external OpenID or social provider, so it cannot play the photo library receiving a federated sign-in or confirm a link before creating it. The planned walkthrough runs once it exists.
G25 SAML. There is no SAML assertion to compare with the ID token.
G17 OpenID Connect logout. There is no RP-initiated, front-channel or back-channel logout to coordinate step 7 across applications.
G66 Second lab tenant: this lab uses Lab Mail, a second tenant. Additional tenants currently need a paid subscription or a BTL grant, so an ordinary learner can do only the Lab Photos steps until every learner can have a second lab tenant.