SSO Client Guidelines
How to act as an SSO client of the auth platform (HAP): an application that lets a user who is already signed into a sibling app (the broker) sign in to your app without re-entering credentials — by obtaining a brokered authorization code and completing a standard OIDC exchange.
This is the client half of a two-role pattern; the other half is the SSO Broker Guidelines. Read both.
Why a broker? HAP is headless and cookie-less, so you cannot rely on a shared browser session (
prompt=nonereturnslogin_required). Instead, the broker app — which owns the user's session — hands you a normal OIDC authorization code. From your side it is ordinary OIDC authorization-code + PKCE; only the source of the code differs (the broker, not a HAP login page).
Build once, switch by config
Your client code is the same whether the authorization endpoint is a broker handoff URL or the platform's Hosted Login endpoint (Hosted Login Guidelines) — both speak standard OIDC Authorization-Code + PKCE. Treat the authorization endpoint as configuration (ideally read it from per-tenant discovery), and you can move between SSO-broker and Hosted Login without code changes. Write a plain, standards-compliant OIDC client; see the Client Cookbook for examples.
Prerequisites
- Your app is registered as a HAP application in the same tenant as the broker. You have a
client_id, aclient_secret(register as a confidential web app), and your callback registered as a redirect URI (exact match). See the Integration Guide. - You know the broker's handoff URL (e.g.
https://<broker-host>/sso/handoff) and the broker has allowlisted yourclient_id+ redirect URI. - You know the tenant's public issuer,
{HAP_BASE}/t/{tenant}— used foriss/aud/JWKS.
The flow
1. (server) Generate a PKCE pair (verifier + S256 challenge) and a random `state`. Store both in
the user's pending-login session (server-side). KEEP THE VERIFIER SERVER-SIDE.
2. Redirect the browser to the broker's handoff URL:
https://<broker-host>/sso/handoff
?client_id=<your client_id>
&redirect_uri=<your callback, exact match>
&code_challenge=<challenge>&code_challenge_method=S256
&state=<state>
3. The broker (if the user has a session there) returns the browser to your callback:
<your callback>?code=<CODE>&state=<state>
…or, on failure: <your callback>?error=<error>&state=<state>
4. (callback, server) Verify `state` matches what you stored. Exchange the code at HAP — server to
server, with your client_secret and the stored verifier:
POST {HAP_BASE}/t/{tenant}/oauth2/token
grant_type=authorization_code
code=<CODE>
redirect_uri=<your callback> # must equal step 2
code_verifier=<verifier>
client_id=<your client_id>
client_secret=<your client_secret>
5. Verify the returned id_token against the tenant JWKS ({HAP_BASE}/t/{tenant}/oauth2/jwks):
check iss = the public issuer, aud = your client_id, exp, and (if you sent one) nonce.
6. Key your local user to the stable `sub` (the HAP identity id); JIT-provision on first arrival.
Use `email`/`name` claims for display. Mint your own application session.You now have a signed-in user with the same identity the broker authenticated.
Fallbacks
- No broker session. If the user isn't signed into the broker, the broker runs its own login
first (then resumes) or returns
?error=login_required. Handle that error by showing a "sign in" path — either send the user to the broker to log in, or run your own primary login (you are a full HAP app too; you can authenticate users directly via OTP/password per the Integration Guide). - Access denied / step-up. If you are a
restrictedapp and the user isn't entitled, or a higher assurance level is required, you'll receive?error=access_denied/interaction_required. Show the appropriate "no access" or "verify it's you" screen — these are not login errors.
Rules
- Same tenant as the broker — cross-tenant SSO is impossible (isolated identities/keys).
- PKCE is mandatory and yours. Generate the verifier/challenge; never send the verifier to the broker (only the challenge). This is what guarantees the brokered code is usable only by you.
- Verify
stateon the callback (CSRF), and verify the id_token (iss/aud/exp/sub) against the public issuer — never trust claims unverified. - Exact
redirect_urieverywhere (handoff, token exchange, registration) — they must all match. client_secretis a secret — inject it from your platform secret store, never commit it.- Key on
sub, not email.subis the stable identity id; email can change (subscribe toidentity.email_changed).
Lifecycle & revocation
- Re-validate periodically with your refresh token: a refresh failure is a reliable signal the identity was suspended/deleted or access revoked — sign the user out.
- For prompt propagation, subscribe to identity-lifecycle webhooks
(
identity.suspended/reinstated/deleted/email_changed) and act on them; keep refresh re-validation as the backstop.
Deploying in the same cluster as the auth service
If your app runs in the same Kubernetes cluster as HAP, use split-horizon addressing (see
API Reference §1): make HAP HTTP calls (token exchange, JWKS, discovery) over
the in-cluster Service (pods can't reach the cluster's own public load balancer), but keep the
public issuer for iss/aud validation. The broker handoff redirect, by contrast, is a
browser redirect, so it uses the broker's public URL.