Skip to content

docs: add Enterprise SSO guide for SSO administrators - #340

Open
sea-snake wants to merge 12 commits into
mainfrom
docs/guides-authentication-single-sign-on
Open

docs: add Enterprise SSO guide for SSO administrators#340
sea-snake wants to merge 12 commits into
mainfrom
docs/guides-authentication-single-sign-on

Conversation

@sea-snake

@sea-snake sea-snake commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Internet Identity can authenticate an organization's staff against its own OpenID provider, but nothing in the docs described how to set that up. Adds a guide for the SSO administrator, and the application developer's half to the existing Internet Identity page.

New page: Single sign-on

docs/guides/authentication/single-sign-on.md, order: 2, so it sits directly after Internet Identity in the Authentication group (Verifiable credentials moves to order: 3).

Follows an existing internal one-pager, and stays at that level of detail:

  • Switch on SSO for the organization (required, one time): register an OIDC client, then publish /.well-known/ii-openid-configuration on the company domain.
  • Control access per application (optional, per app): a second client, the provider-side assignment that acts as the access rule, and one app_clients line.
  • The complete file, with the per-app fields, hiding an app name behind a salted hash, and gate_all_apps.

Internet Identity page

Adds ### One-click SSO sign-in after the existing OpenID section (ssoDomain, mutual exclusivity with openIdProvider, and isValidSsoDomain with its abort and timing behaviour), a #### SSO-scoped attributes subsection, and a Next steps link to the new page.

Verification

The field names, the app_clients key forms, and the gate_all_apps behaviour are checked against the Internet Identity implementation. internet-identity is not among the .sources/ submodules, so the page cites the upstream path it tracks in a trailing comment.

The identity-provider-specific setting (the Entra ID note) cannot be verified from ICP sources and carries a Needs human verification comment.

npm run build passes, and both pages render.

🤖 Generated with Claude Code

Internet Identity can authenticate an organization's staff against its
own OpenID provider, but nothing documented how to set that up. The new
page is written for the administrator of the identity provider: register
a client, publish the discovery file, and optionally govern access per
application.

Also adds the application developer's side to the Internet Identity
page: the `ssoDomain` option, validating a user-typed domain with
`isValidSsoDomain`, and SSO-scoped attributes. The two pages link to
each other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sea-snake
sea-snake requested review from a team as code owners August 4, 2026 14:04
@sea-snake
sea-snake requested a lite review from Copilot August 4, 2026 14:04
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

🤖 Here's your preview: https://z4vwg-raaaa-aaaam-aiiga-cai.icp0.io

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds administrator-facing documentation for configuring Internet Identity single sign-on (SSO) via an organization’s OpenID Connect provider, and updates the existing Internet Identity guide with the corresponding application-side integration details. Also adjusts sidebar ordering so the new guide sits next to the existing authentication docs.

Changes:

  • Added a new SSO guide covering OIDC client setup, the /.well-known/ii-openid-configuration discovery file, per-app access control, and troubleshooting.
  • Updated the Internet Identity guide with ssoDomain usage, isValidSsoDomain, and SSO-scoped attribute key examples.
  • Moved “Verifiable credentials” down one slot in the Authentication sidebar order.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
docs/guides/authentication/verifiable-credentials.md Updates sidebar ordering to make room for the new SSO guide.
docs/guides/authentication/single-sign-on.md New administrator-focused SSO setup guide (OIDC client + discovery file + optional per-app access control).
docs/guides/authentication/internet-identity.mdx Adds application-side “one-click SSO sign-in” and SSO-scoped attribute documentation, plus Next steps link.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/guides/authentication/single-sign-on.md Outdated
Comment thread docs/guides/authentication/single-sign-on.md Outdated
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (4)

docs/guides/authentication/internet-identity.mdx:161

  • The docs assert a precise timing guarantee ("never resolves in under 750 ms"). That kind of implementation detail is brittle and can become incorrect across SDK versions. Consider describing the behavior without hard-coding a minimum duration.
Call `controller.abort()` when the input changes. An aborted check rejects instead of returning `false`, so a superseded check is never read as an invalid domain. The check also never resolves in under 750 ms, which keeps a partially typed domain from flashing an error on every keystroke.

docs/guides/authentication/single-sign-on.md:22

  • The navigation path "Create App Integration → OIDC → Web Application" is Okta-specific UI wording, but the guide is written as if it applies to any IdP. Consider making the instruction generic and optionally calling out Okta as an example so Entra/other IdPs aren’t misled.
Create App Integration → **OIDC** → **Web Application**.

docs/guides/authentication/internet-identity.mdx:134

  • This sentence says nothing has to be registered on either side, but the SSO flow still requires the organization to register an OIDC client in its IdP. Reword to avoid contradicting the new SSO admin guide and to clarify that it’s the application that does not need a client registration with the org’s IdP.
To send the user to their organization's own OpenID provider instead, pass `ssoDomain` with the organization's domain. Internet Identity resolves the provider from a configuration file the organization publishes on that domain, so nothing has to be registered on either side:

docs/guides/authentication/internet-identity.mdx:148

  • This snippet creates a new AuthClient, but only imports isValidSsoDomain. Readers copying the snippet will hit a missing import for AuthClient; either include it here or remove the import line entirely.
import { isValidSsoDomain } from "@icp-sdk/auth/client";

sea-snake and others added 2 commits August 4, 2026 16:28
The three steps are now the top-level sections, so the page outline is
the flow and the duplicate list in the intro is gone. Per-app access
keeps its steps together with the gate and the hashed-key recipe, and
the full file moves to the end as reference.

The hashed-key section now says where to run the snippet and what to do
with what it prints.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Names the audience rather than the mechanism, so a company scanning the
sidebar can see the page is about connecting their own provider. The file
is renamed to match the title, and the code fence gets a language tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sea-snake sea-snake changed the title docs: add single sign-on guide for SSO administrators docs: add Enterprise SSO guide for SSO administrators Aug 4, 2026
sea-snake and others added 8 commits August 4, 2026 16:33
Drops the Next steps link to it and restores its sidebar order, so the
page is untouched by this branch. Enterprise SSO still sorts ahead of it
within the Authentication group.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It appeared only in the Entra note and then unannounced in the complete
file. It exists because a per-app client can change the sub a provider
issues for the same person, so it is introduced in the per-app step, and
the complete file now has a table for the fields that step adds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both were trailing sentences in step 3, so the default-deny switch read
as a footnote and the subject claim did not state its precondition. Each
is now a subsection: what happens to unlisted apps and which default to
pick, and the pairwise-sub case that only arises once an app has its own
client.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both sat in one aside, where a reader skimming the steps would miss them.
Assignment required now sits in the assignment step it modifies, since
missing it leaves an app open to the whole tenant, and the oid claim sits
with the subject-claim section. No aside remains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Gives admins a way to say how long a sign-in stays valid before staff
authenticate again. Named after the OIDC max_age parameter, in seconds,
and documented as a cap on whatever lifetime an application asks for.

The field is pending implementation in Internet Identity: the section
carries a comment saying so, and it must not be published before the
canister supports it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The block appeared twice, split by a blank line, and the lead-in still
referred to step 3 after the session field was added in step 2. One block
now, in field order, with no blank lines inside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The field defaults to 28800 rather than being unset, so leaving it out
caps a sign-in at eight hours instead of deferring to whatever the
application asks for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sea-snake added a commit to dfinity/internet-identity that referenced this pull request Aug 7, 2026
Top of stack, stacked on #4189.

# Motivation

The sign-in screen and the relying party both key re-authentication off
delegation expiry. Without this, an organization could set an eight-hour
session and a dapp holding a 30-day delegation would carry on working
long past it, with only attribute verification failing.

# Changes

- The sign-in flow carries the organization's session length from the
discovery result onto the authenticated session, for both the
fresh-sign-in and last-used paths.
- The delegation handler bounds the `maxTimeToLive` it requests for the
account delegation by that value, via `cappedMaxTimeToLive`. An app
asking for 30 days on an eight-hour domain gets eight hours; a shorter
request is left alone.

This is a **UX cap, not a security boundary**, and it is applied in the
frontend deliberately: a delegation identifies the identity rather than
the SSO session, so any other access method on the anchor can mint a
fresh one regardless. The organization's deadline is enforced by the
expiry inside the certified attribute bundle (#4189).

# Tests

- New unit tests for the clamp: non-SSO sessions pass through, a longer
request is capped, a shorter request is untouched, and the
organization's value applies when nothing was requested.
- `tsc --project tsconfig.all.json`: clean. `eslint`: clean. Frontend
unit tests pass.

Administrator-facing documentation is in
dfinity/developer-docs#340.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@marc0olo

marc0olo commented Aug 11, 2026

Copy link
Copy Markdown
Member

Review: Enterprise SSO

Verified against .sources/internetidentity at release-2026-08-07 (c78d1b99), the pin main picked up in #341.

Must fix

  • Sidebar order collision: the new page sets order: 2, and docs/guides/authentication/verifiable-credentials.md already uses order: 2. The PR description claims VC moves to order: 3, but the diff never touches it. Bump VC to 3.

  • Config changes are not immediate, and the page never says so: discovery entries cache for one hour (FRESH_FOR_SECONDS, sso.rs:72), with another hour of stale-if-error fallback (STALE_FOR_SECONDS, sso.rs:75). On a page whose central promise is gating access app by app, an admin will assume that removing an app_clients entry revokes access at once. It can take up to an hour, or two if a refetch fails. This belongs in step 3.

  • A gated app cannot be a user's first sign-in: resolve_gated_ii_client_sub returns None (surfacing as NoSuchAnchor) unless an identity was already established through the org's II client on that domain. sso_gating.rs:163 says it outright: "sign in normally first." A new employee sent straight to a gated app fails, with no explanation in this guide.

  • Out-of-bounds values reject the whole file, not just the field: more than 100 app_clients entries (MAX_APP_CLIENTS, sso.rs:48, rejected rather than truncated, deliberately), a session_max_age_seconds of 0 or above 2592000, or a file over 64 KiB (DISCOVERY_MAX_RESPONSE_BYTES, sso.rs:94) all make validate_ii_config fail, which takes SSO down for the entire domain. The guide presents the 30-day figure as a ceiling, which reads as "it clamps." It does not. One sentence per limit, plus the 100-entry cap, which is not mentioned at all.

  • PR description does not match the PR: it names the file single-sign-on.md (it is enterprise-sso.md), claims a Verifiable credentials reorder that is absent, and states that internet-identity is not among the .sources/ submodules when .sources/internetidentity exists and is pinned. It also carries an AI attribution line, which the repo rules ban.

Suggestions

  • Say what a domain may look like: validate_discovery_domain (sso.rs:378) requires a bare authority that round-trips exactly through the URL parser (is_bare_authority, sso.rs:400), so https://acme.com, acme.com/, and Acme.com are all rejected. The guide says "enter acme.com" without signalling that this is a constraint rather than a formatting choice.

  • app_clients keys are exact origins: matching is byte-equality against the app's origin, so a trailing slash, a path, or a scheme mismatch silently falls through to the org-wide client. The failure is invisible, so it deserves a line.

  • Explain why the CORS header is needed: Access-Control-Allow-Origin: * matters only for the client-side pre-check. The canister fetches the file via HTTPS outcalls and is unaffected. As written, an admin may believe SSO breaks without it.

  • Caution on changing stable_identifier_claim later: it changes how identities key onto anchors, so flipping it on a live domain is a migration, not a config tweak. Worth confirming the intended behaviour before wording it.

  • Anchor the cross-link: Next steps points at internet-identity.md bare. internet-identity.md#one-click-sso-sign-in lands on the half that answers "how do applications send users into this flow."

  • Numbering vs the intro: "Setup is two steps" followed by headings 1., 2., 3. reads slightly against itself. "Two required steps, plus an optional third" would remove the stumble.

Verified

  • Neither file is on a sync path. sync-ii-spec.yml writes only docs/references/internet-identity-spec.md, docs/references/verifiable-credentials-spec.md, and public/references/internet-identity.did.
  • Field names, defaults, and semantics against sso.rs and sso_gating.rs: client_id, openid_configuration, name (falls back to the domain), app_clients, gate_all_apps, stable_identifier_claim (default sub), session_max_age_seconds (default 8h via DEFAULT_SESSION_MAX_AGE_NS, ceiling delegation::MAX_EXPIRATION_PERIOD_NS = 30 days).
  • The salted-hash recipe: sha256(origin || salt) with the salt hashed as its hex string, keyed as <hex>:<hex>, cleartext and hashed keys mixable in one file. The shell snippet produces exactly what AppClientKey::parse accepts.
  • OIDC client settings: response_type=code id_token with response_mode=form_post, no access token, redirect https://id.ai/callback, default scopes openid/profile/email.
  • sso:<domain>:<key> attribute keys, and that verified_email is deliberately unavailable under sso: (attributes.rs:468), matching the page's reasoning.
  • session_max_age_seconds is live on mainnet: proposal 143403 executed 2026-08-10, module f6c0abac... = release-2026-08-07.
  • Style rules: no em-dashes, no dfx, no mo:base, no absolute internal links, .md extensions, complete frontmatter, ## Next steps present. All three links resolve.

One release-state note

The internet-identity.mdx additions (ssoDomain, isValidSsoDomain, scopedKeys({ ssoDomain })) come from dfinity/icp-js-auth#141, which is still open. The latest published @icp-sdk/auth is 8.0.3 and exposes none of them: scopedKeys accepts only openIdProvider. The canister half of this PR is fully live, so that section is the only part gated on an external release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants