docs

Single sign-on and SCIM

Sign members in through your OpenID Connect identity provider, require it, and provision or remove members with SCIM 2.0.

Admins connect an identity provider under Settings, Identity. Single sign-on decides how members sign in. SCIM keeps the member list in step with the identity provider.

Single sign-on

Unhandled signs people in with OpenID Connect, using the authorization code flow with PKCE. It works with providers that publish OpenID discovery metadata, such as Okta, Microsoft Entra ID, Google Workspace and Keycloak. SAML is not supported yet.

  1. In your identity provider, create a web application. Set its redirect URI to https://unhandled.net/sso/callback and grant the openid, email and profile scopes.
  2. In Settings, Identity, enter the issuer URL, the client ID, the client secret and your email domain, then Save. The secret is stored encrypted and never shown again.
  3. Add the TXT record the page shows, _unhandled-sso.<domain> with the value unhandled-sso=<token>, then select Verify domain.
  4. Select Test sign-in with SSO to sign in once through the identity provider.

People sign in at /sso with their work email, or with Sign in with SSO on the sign-in page.

Who the identity provider can sign in

The identity provider's subject identifies the person. An email address links an Unhandled account only in these cases:

  • The address is at the verified domain and the identity provider marks it verified. A new address gets an account and joins the organization as a member. An existing account is linked. If that account never verified its address, its password, passkeys, GitHub link, sessions and MCP tokens are revoked first, since someone else may have registered it.
  • A member who is already signed in to Unhandled starts SSO for the organization. This links contractors and others whose address is at another domain.

Anyone else is refused. An identity provider can never sign in an address outside its verified domain.

Require single sign-on

With Require single sign-on on:

  • Members whose address is at the verified domain, and members who linked an identity provider account, cannot sign in with a password, a passkey or GitHub. Unhandled sends them to the identity provider instead.
  • Every member must have signed in through the identity provider in the current browser session before they can open the organization. Other requests return 403.
  • The MCP consent page, where you connect Claude Code, Cursor or another agent, sends anyone who belongs to the organization through the identity provider first.

You can only turn it on after the domain is verified and you signed in through the identity provider in this session, so a broken setup cannot lock everyone out. A session that comes back from Remember me has to sign in through the identity provider again.

The issuer must use https:// and resolve to a public address. A self-hosted instance can exempt hosts, such as a local Keycloak, with the SSO_TRUSTED_HOSTS environment variable, a comma-separated list of host:port values.

Single sign-on is available on every plan.

SCIM

Unhandled serves SCIM 2.0 Users at https://unhandled.net/scim/v2. It implements the parts of RFC 7644 that Okta and Microsoft Entra ID use to provision users.

  1. Open Settings, Identity and select Create SCIM token. Copy the token. It is shown once.
  2. In your identity provider, set the base URL to https://unhandled.net/scim/v2 and the authentication to a bearer token with that value.
  3. Assign people to the app. Each userName must be their email address.

Each organization has one token. Rotate replaces it, and the old one stops working at once. Revoke deletes it.

Provisioning

  • Someone who is already a member is linked to the SCIM user.
  • Anyone else gets the same emailed invitation an admin would send, as a member. They join when they accept it, so Unhandled never takes an address on the identity provider's word.
  • Identity providers look users up with filter=userName eq "..." or filter=externalId eq "...". Other filters return 400 invalidFilter.

Deprovisioning

Setting active to false (with PATCH or PUT) or deleting the user removes them from the organization at once and revokes, in every organization they belong to:

  • their MCP connections and OAuth grants, including unredeemed authorization codes;
  • the agent identities they created;
  • their browser sessions.

A pending invitation for that address is withdrawn. The organization's last admin cannot be deprovisioned, and that request returns 409.

Deprovisioning only reaches people who are members of the organization that owns the token. A token never reads or changes another organization's SCIM users.

Supported operations

RequestResult
GET /ServiceProviderConfigPatch and filter supported, bulk, sort and ETags not
GET /UsersPaged with startIndex and count, at most 100 per page
POST /UsersProvisions, 409 uniqueness when the userName exists
GET /Users/{id}The user, or 404
PUT /Users/{id}Replaces userName, externalId, displayName and active
PATCH /Users/{id}add, replace and remove on those attributes, with or without a path. String booleans such as "False" are accepted
DELETE /Users/{id}Deprovisions and deletes, 204

Requests are limited to 300 a minute per token.