SSO Broker Guidelines
How to act as an SSO broker for the auth platform (HAP): an application that already holds a user's authenticated session and brokers authorization codes for sibling applications, so a user signed into your app can open another app in the same tenant without signing in again.
This is the broker half of a two-role pattern; the other half is the SSO Client Guidelines. Read both.
When to use this. HAP is headless and cookie-less — there is no shared browser session at the auth domain, so
prompt=nonesilent SSO is not available. A broker fills that gap: it is the app that owns the login experience and the user's session, and it hands sibling apps a standard OIDC authorization code on demand. (A future platform feature may add a hosted session so apps can talk to HAP directly; until then, the broker is the SSO hub.)
One of three modes (same client contract)
A client app can sign users in three ways — embedded (it renders its own UI;
Integration Guide), SSO broker (this doc), or Hosted Login (the
platform renders the UI; Hosted Login Guidelines). The broker and
Hosted Login present the identical client-facing contract — standard OIDC Authorization-Code +
PKCE — differing only in which authorization endpoint the client points at: this broker's handoff
endpoint, or the platform's hosted endpoint. A client switches between them by configuration, not
code. That means your broker's /sso/handoff endpoint is effectively an authorization-endpoint
shim: accept the standard authorize parameters and return a code (or error) via redirect, so an
unmodified OIDC client works against it. See the Client Cookbook.
Prerequisites
- Your app is already a registered HAP OIDC application and runs its own primary login (password,
email-OTP, etc.) — see the Integration Guide. After login it holds a
short-lived
login_token(the session token HAP returns from/v1/auth/.../verifyor/v1/auth/login). - The sibling (client) apps you broker for are registered in the same HAP tenant as you. Tenants are isolated (separate identities, keys, sessions) — a broker can only broker within its own tenant.
The two things you must add
1. Retain the user's login_token server-side
By default an app uses the login_token once (to do its own authorize→token) and discards it. To
broker, you must keep it in your server-side session store for as long as the user's session
lasts. It is:
- multi-use and valid for the session lifetime (not single-use),
- not bound to the application that obtained it — which is exactly why you can use it to mint a code for a different client.
Never expose this login_token to the browser (no cookies readable by JS, never in a URL or
response body). It is a bearer session credential for the whole tenant; keep it server-side only.
2. A handoff endpoint
Add a server-side endpoint (this guide calls it GET /sso/handoff) that a client app redirects the
browser to. Suggested contract:
| Query param | From | Meaning |
|---|---|---|
client_id | the client app | the HAP client_id of the app requesting sign-in |
redirect_uri | the client app | where to send the browser back (must be that client's registered URI) |
code_challenge + code_challenge_method=S256 | the client app | the client's PKCE challenge (public half) |
state | the client app | opaque CSRF value; you pass it back unchanged |
scope | the client app (optional) | defaults to openid profile email |
Behaviour:
-
Require your own session. If the user is not authenticated with your app, run your normal login first (then resume the handoff). Never broker for an unauthenticated user.
-
Allowlist the request. Accept
client_id+redirect_urionly for apps you explicitly trust. Do not broker for arbitraryclient_id/redirect_urivalues — that turns you into an open redirector and a token-phishing vector. (HAP also enforces exactredirect_urimatch per client, but validate it yourself too.) -
Broker the code server-side. Call HAP's authorize endpoint from your server with the client's parameters and your retained
login_token:GET {HAP_BASE}/t/{tenant}/oauth2/authorize ?response_type=code &client_id=<client app's client_id> &redirect_uri=<client app's redirect_uri> &scope=openid profile email &code_challenge=<client's challenge>&code_challenge_method=S256 &login_token=<the user's retained login_token> &consent=grantedHAP returns a redirect carrying a one-time
codefor the client'sclient_id. Extract it. -
Redirect the browser to the client.
302to{redirect_uri}?code=<CODE>&state=<state>. On an auth error (e.g. the access gate denies arestrictedapp), forward{redirect_uri}?error=<error>&state=<state>instead.
Why this is safe (PKCE makes you a conduit, not a holder)
The PKCE verifier never reaches you — the client keeps it and only sends you the challenge.
HAP binds the brokered code to that challenge, so only the client can exchange the code (it
needs the verifier). You hand off a code you yourself cannot redeem. That's the key property:
the broker moves a single-use, client-bound code; it never mints tokens for the client and never
leaks the powerful login_token.
Rules
- Same tenant only. You can only broker for clients registered in your tenant.
- Allowlist clients + exact redirect URIs. No open brokering.
login_tokenstays server-side. Never in a URL, cookie-readable store, or response body.- Pass
statethrough untouched (the client's CSRF protection). consent=grantedis you asserting first-party consent. Only broker for first-party ecosystem apps you control. For third-party apps a real per-app consent step is required and the broker pattern is not appropriate.- Honour session end. If your retained
login_token/session is expired or revoked, treat the handoff as unauthenticated (re-login). When the user logs out of your app, drop the retainedlogin_token. - Step-up / restricted apps. If a client needs a higher assurance level (
acr_values) than the session holds, or is arestrictedapp the user isn't entitled to, HAP returnsinteraction_required/access_denied— forward these to the client as?error=….