Single sign-on
Ta treść nie jest jeszcze dostępna w Twoim języku.
Single sign-on lets an external identity provider (IdP) decide who may sign in to your Control Center server. Two protocols are supported — SAML 2.0 and OpenID Connect — and both land in the same place: a freshly minted device credential that is indistinguishable from an invite or QR pairing everywhere downstream.
The shape of a login
Section titled “The shape of a login”SAML logins are browser-mediated. The IdP never opens a connection to your server; the user’s browser carries everything:
browser ──> /saml/login ──> IdP ──POST SAMLResponse──> /saml/acs │ verify ▼ provision user (maybe) │ ▼ browser handoff: device PSK in the URL fragment (never a query parameter)Because the browser is the courier, a server behind NAT works fine for logins: localhost, LAN, Tailscale, VPN, or a tunnel all serve — only the user’s browser must reach the ACS URL. The WebSocket relay used by phone remote control cannot carry the HTTP round-trip, which is why SSO entry points always target the server’s HTTP origin.
OpenID Connect follows the same shape with an authorization-code + PKCE round-trip; claims are read from the issuer’s token endpoint over TLS, so the browser never supplies identity data at all.
The adapters
Section titled “The adapters”Control Center splits the responsibility deliberately:
| Piece | Where | Owns |
|---|---|---|
cc_saml native |
packages/cc_natives/native/saml/ |
XML-DSig verification, canonicalization, SAML profile validation — delegated to the pinned pure-Rust saml crate behind a stateless C ABI |
SamlService / OidcService |
cc_server_core |
login state (pending request trackers, replay dedupe), validation policy knobs, the shared provisioning call |
SsoProvisioner |
cc_server_core |
find-or-create the user, pin the provider subject, grant memberships under the auto-member policy |
ScimService |
cc_server_core |
the SCIM 2.0 push surface (provision/deprovision lifecycle) |
SsoSettingsService |
cc_server_core |
the saved connection rows, live re-apply, the SCIM token |
There is deliberately no pure-Dart SAML crypto fallback: hand-rolled
XML-DSig/canonicalization is exactly where signature-wrapping
vulnerabilities live, so a missing cc_saml library refuses both SAML
login and server boot. The native is stateless by design — cross-request
state lives in the Dart services, single-use and short-lived.
What a login verifies
Section titled “What a login verifies”Inside the verified native and the service, in order:
- the assertion’s signature (always required; the response envelope’s is optional per configuration) — structurally resistant to signature-wrapping because the validated payload is bound to the verified element,
- the issuer matches the configured IdP,
- the audience names this service provider,
- the destination is our ACS URL,
InResponseTomatches a pending AuthnRequest we issued (unsolicited, IdP-initiated responses are off by default),- the lifetime windows hold within the clock skew (default 90s) and
- the assertion id was never seen before (replay cache).
Failures come back as typed codes — signature, expired, audience, destination, issuer, request-match — and the endpoints answer with generic errors, so an attacker learns nothing about why a forgery failed.
Accounts and subject pinning
Section titled “Accounts and subject pinning”The first SSO login pins the provider’s immutable subject (issuer plus
SAML NameID / OIDC sub, or SCIM externalId) onto the account. From then
on, the subject — not the email — identifies the login. If someone else
later presents the same email from the same or another IdP, the pinned
account refuses them. This is what stops a recycled email at a provider from
becoming an account takeover.
Provisioning and deprovisioning
Section titled “Provisioning and deprovisioning”JIT provisioning happens at login (SsoProvisioner). SCIM is a separate
push surface (ScimService) that pins the same subject on the user row;
it does not grant workspace memberships. Deprovisioning — SCIM
active: false or DELETE — refuses if the account is a workspace owner
(transfer first). Otherwise it revokes every device credential first
(sessions drop within seconds through the device watch), removes every
workspace membership and stamps the account deactivated. The user row
survives: attribution is permanent. Reactivation restores login but never
re-grants memberships.
What stays out
Section titled “What stays out”- Group→role mapping happens at login from the SAML attributes / OIDC claims — pushed SCIM groups are not stored.
- The owner role is unreachable from SSO — neither as a default role nor through a group mapping.
- The WebSocket relay is never an SSO channel; phones that reach the server only through the relay cannot complete a browser round-trip against it.