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

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.

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.

  1. Get a code for the printer without a second password

    Recorded as oauth.authorize succeeded (code_issued) for lab-printer about [email protected].

  2. Sign Ava out at the identity provider

    Recorded as account.sign_out succeeded (signed_out).

  3. See a silent sign-in refused after sign-out

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

  4. Disable Cora through provisioning

    Recorded as scim.user.lock succeeded about [email protected].

  5. See Cora's existing access token refused

    Recorded as oidc.userinfo rejected (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.

  1. On this lab page choose Lab Photos and press Start.

  2. 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)"; }
  1. 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, then SCIM_TOKEN).

  2. 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

  1. Map the roles. Lab Photos is the employer's identity provider. The Token Decoder plays the training portal, and lab-printer plays the photo library. Both are relying parties.

  2. In a private window, sign in through $ISSUER/token-decoder as Ava with openid profile email, using her password and authenticator code. Note the ID token's iss, sub and aud, which is the Token Decoder's own client ID. Keep the ID token for Break it:

read -rs DEC_IDT
  1. In the same window, run fresh; authz openid%20profile and 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.

  1. In Audit, the oauth.authorize event for lab-printer shows code_issued with no user_signed_in before it.

  2. Connect a sign-in to an account. In another private window, sign in through $ISSUER2/token-decoder as Lab Mail's Ava. Compare the two ID tokens: the email is the same, while iss and sub both 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.

  1. Fresh authentication: in the first window run fresh and open $(authz openid)&max_age=0. You must enter Ava's password and code again, and auth_time in the new ID token changes.

Why it matters: an application can ask for more than the provider's existing session.

  1. 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).

  1. 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.

  1. Account linking that exists today: in the Establishing an identity lab, lab-provisioning linked 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).

  1. 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.

  2. 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.

  3. Remove the link and confirm that a Lab Mail sign-in no longer reaches Ava's photos.

  4. 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

  1. 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.authorize succeeded with reason code_issued for lab-printer and Ava.

  • account.sign_out succeeded with reason signed_out.

  • oauth.authorize rejected with reason login_required for lab-printer.

  • scim.user.lock succeeded for Cora.

  • oidc.userinfo rejected with reason invalid_token.

Cleanup

  1. Re-enable Cora with the same PATCH and "value":true. Audit shows scim.user.unlock.

  2. Delete printer.json and run unset AT CORA_AT DEC_IDT.

  3. 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.

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