Use case — Multi-account chooser
What is prompt=select_account?
OIDC Core 1.0 §3.1.2.1 defines a prompt request parameter that the RP sends with /auth. Three values matter here:
prompt= | What it asks the OP to do |
|---|---|
none | Show no UI: answer from the active session, or fail. |
login | Force a fresh login even if a session is active. |
select_account | Show an account chooser — the user picks which account to continue with. |
select_account is what the "switch account" button on large SaaS products sends. The user has several accounts signed in to the same browser (work and personal, or alice and bob); the OP lists them and lets the user choose. The chooser appears only when the browser already has an active session; without one the request goes straight to login.
The library implements this as a chooser group: the set of sessions one browser is signed into, kept by the OP's session manager, which can add an account to the group, switch the active one, and sign out the whole set.
RP-Initiated Logout works on that group. /end_session without logout_scope destroys every session in the chooser group. logout_scope=current (the only accepted value, sent once) destroys only the active session and points the cookie at a surviving sibling. An empty, unknown, or repeated logout_scope returns HTTP 400 and leaves the group untouched. An id_token_hint that does not match the session still requires the user's confirmation; it cannot be used to skip it. A matching hint skips the confirmation only when the logout ends that account alone; when it would also end other accounts signed in on the same browser, the OP asks the user to confirm.
Specs referenced on this page
- OpenID Connect Core 1.0 — §3.1.2.1 (
promptparameter), §3.1.2.4 (interaction with consent) - OpenID Connect Back-Channel Logout 1.0 — fan-out when "log everyone out" fires
Vocabulary refresher
promptparameter: a hint the RP sends with/authabout whether to show UI:none(silent: answer from the active session or fail),login(force a fresh login),consent(force the consent prompt),select_account(show the account chooser). Several values may be space-separated.- Chooser group: the sessions one browser is signed into, which large SaaS products show as the "switch account" menu. The OP stores the group server-side; the cookie ties the browser to the group, not to a single session.
sub(subject): the stable identifier of the user, scoped to the OP and RP pair. Switching accounts in the chooser changes thesubin the nextid_token: same browser, different identity.
Sources:
examples/13-multi-accountfor the JSON-driver chooser flow, andexamples/12-custom-chooser-uifor the HTML template path.
How it works
prompt=login: that would replace the current account. It uses the add-account link the chooser hands out, whose private markers tell the OP to join the group the cookie points at.Wiring
The library ships a built-in interaction for prompt=select_account. It emits an interaction.ChooserPromptData listing every account in the active chooser group plus an AddAccountURL. With the default HTML driver the bundled template renders the list and the user POSTs back the chosen SessionID. Pass op.WithUI(op.HTMLUI{Chooser: op.PageTemplate{Template: tmpl}}) to keep that server-rendered flow with your own template.
With the JSON driver (op.WithUI(op.DriverUI{Driver: interaction.JSONDriver{}})), the SPA receives the same data as JSON and posts back the SessionID. Under op.SPAUI the SPA owns the chooser the same way.
Adding an account needs the AddAccountURL from the prompt, not a hand-built URL. It is the original /auth request with prompt=login plus private markers naming the chooser group, and the OP honours the markers only while the browser still presents that group's cookie. A bare prompt=login replaces the current account. Under RequirePAR (for example FAPI 2.0) the OP leaves AddAccountURL empty, because a query-string /auth URL would be refused; hide the "add account" link in that case.
The session manager is internal; application code does not call it. It performs four operations as the OP handles /auth and the chooser interaction:
| Operation | When |
|---|---|
| Open a new chooser group | first login in a browser |
| Add an account to the group | a sign-in started from AddAccountURL |
| Switch the active session | the user picked an account in the chooser |
| End the whole group | sign out everyone (/end_session without logout_scope) |
The only session interface you implement is store.SessionStore (Save, Find, Touch, Delete, ListByChooserGroup), when you supply your own session backend.
After a pick, the usual request checks still apply. If the chosen session is stale for the request's max_age, does not meet its acr_values, or the request carries prompt=login, that account is asked to authenticate again instead of the request failing. The picked account also goes through the LoginFlow rules, the Decider and risk step-up, as a returning session does. A pick that re-authenticates rotates the picked session with the new auth_time, acr and amr.
Read next
- Custom chooser UI — keep the chooser server-rendered but replace the account picker template.
- SPA / custom interaction — drive the chooser from a SPA.
- Back-Channel Logout — fan-out when the user logs everyone out.