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=none silent 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/.../verify or /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 paramFromMeaning
client_idthe client appthe HAP client_id of the app requesting sign-in
redirect_urithe client appwhere to send the browser back (must be that client's registered URI)
code_challenge + code_challenge_method=S256the client appthe client's PKCE challenge (public half)
statethe client appopaque CSRF value; you pass it back unchanged
scopethe client app (optional)defaults to openid profile email

Behaviour:

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

  2. Allowlist the request. Accept client_id + redirect_uri only for apps you explicitly trust. Do not broker for arbitrary client_id/redirect_uri values — that turns you into an open redirector and a token-phishing vector. (HAP also enforces exact redirect_uri match per client, but validate it yourself too.)

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

    HAP returns a redirect carrying a one-time code for the client's client_id. Extract it.

  4. Redirect the browser to the client. 302 to {redirect_uri}?code=<CODE>&state=<state>. On an auth error (e.g. the access gate denies a restricted app), 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_token stays server-side. Never in a URL, cookie-readable store, or response body.
  • Pass state through untouched (the client's CSRF protection).
  • consent=granted is 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 retained login_token.
  • Step-up / restricted apps. If a client needs a higher assurance level (acr_values) than the session holds, or is a restricted app the user isn't entitled to, HAP returns interaction_required / access_denied — forward these to the client as ?error=….