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

OPENID CONNECT · LAB

Correlate two clients through a public subject, then hand-build a per-sector one

Join the printer's and the collage app's records about Ava through her public sub, see why a prefix does not stop it, and approximate a pairwise sub with a hashed per-sector expression.

Partly readyUses your lab tenant

The lesson

Builds on: The UserInfo endpoint.

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. Create a temporary ID token manager for the printer

    Recorded as tenant.oauth.id_token_managers.create succeeded.

  2. Assign it to lab-printer

    Recorded as tenant.oauth.id_token_managers.assign succeeded.

  3. Change how a manager builds sub

    Recorded as tenant.oauth.id_token_managers.update succeeded.

  4. Test the per-sector expression before saving

    Recorded as tenant.oauth.id_token_managers.test succeeded.

  5. Sign Ava in to lab-collage with the new sub

    Recorded as oauth.token succeeded for lab-collage about [email protected].

  6. Sign Ava in to lab-printer with the new sub

    Recorded as oauth.token succeeded for lab-printer about [email protected].

Setup

  1. You need lab-collage with its lab-collage ID token manager, lab-printer with PRINTER_ID and PRINTER_SECRET, Ava, rp_discover and the redefined helpers, and links.json from the several providers lab. Run rp_discover "$ISSUER" and keep btl-lab callback running.

  2. Press Start.

  3. In OAuth > ID Token Management, create a manager named lab-tmp-printer-id with the default settings and assign it to lab-printer only. It is temporary and is deleted in Cleanup.

  4. Define a sign-in for the printer alongside the collage helpers:

printer_signin_url() { eval "$(btl-lab pkce)"; eval "$(btl-lab state)"; echo "$AUTHORIZATION_ENDPOINT?response_type=code&client_id=$PRINTER_ID&redirect_uri=http%3A%2F%2F127.0.0.1%3A8765%2Fcallback&scope=openid&state=$STATE&nonce=$NONCE&code_challenge=$CHALLENGE&code_challenge_method=S256"; }
printer_exchange() { PRINTER_IDT=$(curl -s -u "$PRINTER_ID:$PRINTER_SECRET" "$TOKEN_ENDPOINT" -d grant_type=authorization_code --data-urlencode "code=$1" --data-urlencode "redirect_uri=http://127.0.0.1:8765/callback" --data-urlencode "code_verifier=$VERIFIER" | jq -r .id_token); btl-lab decode "$PRINTER_IDT"; }

Walkthrough

  1. One identifier for everyone. The discovery document says "subject_types_supported": ["public"]. Sign Ava in through lab-collage (signin_url 'openid', exchange) and through lab-printer (printer_signin_url, printer_exchange '<code>'), and compare the two sub values: identical.

  1. Correlation. Write each company's records and join them on nothing but the value the provider gave both:

jq -n --arg s '<sub>' '{sub: $s, orders: ["photo book", "calendar"]}' > printer.json
jq -n --arg s '<sub>' '{sub: $s, collages: ["summer 2026"]}' > collage.json
jq -s 'group_by(.sub) | map(add)' printer.json collage.json

One profile, built by two unrelated companies.

Why it matters: between unrelated companies, a public sub is a ready-made join key. Your print orders and your collages, given to two separate businesses, end up in one record.

  1. A prefix is not pairwise. On the lab-tmp-printer-id manager, set the standard claim sub to the subject ID with the prefix printer-. On the lab-collage manager, use the prefix collage-. Sign in through both clients. The values now differ, but stripping the prefixes joins the records again, just as the lesson's picture address carried the account number.

  1. A per-sector sub, the lesson's hash of sector, account and secret salt. Generate a salt with openssl rand -hex 16. On lab-tmp-printer-id, set sub to this expression, with your salt in place of <salt>:

(async () => { const d = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('printer.example|' + context.subject.id + '|<salt>'));
  return Array.from(new Uint8Array(d), b => b.toString(16).padStart(2, '0')).join('').slice(0, 32); })()

Use the same expression with the sector collage.example on the lab-collage manager. Run each manager's Test before saving. If a test reports a failure, stop and keep the prefix version from step 3.

Note: the salt is visible to tenant administrators in the expression. A real provider would keep it secret. This step approximates the derivation; it is not the pairwise feature itself.

  1. Sign in through both clients. The two sub values share nothing. Call UserInfo for the lab-collage sign-in and confirm it returns the same sub as that client's ID token.

Why it matters: only the provider can map these values back to Ava. The collage app and the printer have no common key.

  1. Same sector, same value. Change the lab-collage manager's sector string from collage.example to printer.example. Both clients now receive the same sub, as two registrations sharing one sector_identifier_uri would.

  1. Not anonymous. Sign in to both with openid%20email: both receive Ava's email address, and it joins the records just as well as the public sub did.

Planned walkthrough

These steps run once pairwise subjects (G18) exist.

  1. On lab-printer and lab-collage, set subject_type to pairwise. Discovery lists "subject_types_supported": ["public", "pairwise"], and each client receives its own sub for Ava with no expression and no visible salt.

  2. Publish a redirect URI list for the printer's sector and name it as sector_identifier_uri on lab-printer and on a temporary second printer client, lab-tmp-printer-app. Both receive the same sub. The tenant refuses the registration of a client whose redirect URIs are not in the file.

  3. Move lab-printer's sector and see every returning customer arrive with a new sub, then move it back.

Break it

  1. Move a sector. Change the lab-tmp-printer-id sector string to www.printer.example and sign in through lab-printer. Ava's sub matches no entry in printer.json, so every returning customer would look new. The previous values stay bound to Ava in the tenant and will never be given to anyone else.

Restore: set sub on both managers back to the default (the user ID, with no prefix and no expression). That reconnects Ava to the original links.json entry.

Check your work

Press Check my progress. It looks for, in order: the lab-tmp-printer-id manager created and assigned, an update to how a manager builds sub, a successful manager test, then oauth.token succeeded for lab-collage and for lab-printer.

Your notes should show the sub each client received for public, prefixed, per-sector and moved-sector subjects.

Cleanup

  1. Confirm that the lab-collage manager issues the default sub.

  2. Assign lab-printer back to the tenant's default ID token manager, then delete lab-tmp-printer-id.

  3. Delete printer.json and collage.json. Run unset PRINTER_IDT and unset -f printer_signin_url printer_exchange.

Missing infrastructure

  • G18 (pairwise subject identifiers): the tenant cannot issue pairwise subjects. Discovery advertises public only, clients have no subject_type setting, and there is no sector_identifier_uri fetch or validation. The expression in step 4 only approximates the derivation: it is configured by hand per manager, it is not advertised, and its salt is readable by administrators. The planned walkthrough will replace it with real pairwise registrations and a sector file the tenant enforces.

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