Skip to content
Security
Markdown

SSO and SCIM

Sign people in through your organization's identity provider, provision them with SCIM, and map IdP groups to roles.

Connect your organization's identity provider (IdP) and members sign in through it instead of GitHub or Google. With SCIM, the IdP also adds people and groups to Parity and removes them when they leave. Any Admin of the organization sets this up.

Parity connects with OpenID Connect: the authorization code flow with PKCE, requesting the scopes openid email profile. SAML is not supported. Okta, Microsoft Entra ID, Google Workspace, JumpCloud, OneLogin, Ping and Keycloak all support OIDC for custom apps.

Connect an IdP

  1. In your IdP, create an OIDC web application. Register Parity's redirect URI as its sign-in redirect URI:

    Redirect URI
    https://console.parity.ninja/auth/sso/callback
  2. In the Parity console, open Sign-in from the account menu and choose Connect IdP.

  3. Fill in the fields below and choose Connect. Parity reads the issuer's discovery document before it saves, so a wrong issuer fails here rather than at someone's first sign-in.

  4. Sign in at the connection's sign-in URL to finish the check.

FieldValue
NameWhat people see on errors and in the Members list, such as Okta. Up to 60 characters.
Sign-in nameThe organization's name at sign-in, such as acme. Lowercase letters, digits and dashes, up to 40, unique across Parity.
IssuerThe IdP's issuer URL, exactly as its discovery document states it, such as https://acme.okta.com. Must be https on a public address.
Client IDFrom the IdP application.
Client secretFrom the IdP application. Stored encrypted. Leave it empty when editing to keep the stored one.
Groups claimThe ID token claim that lists the person's groups. Default groups. Clear it to ignore groups in the token.
AccessAnyone the IdP signs in (default) or Only people provisioned by SCIM. See Access.
Default roleThe role a person gets the first time they sign in. Default Viewer.

Parity authenticates to the IdP's token endpoint with HTTP Basic (client_secret_basic). It accepts ID tokens signed with RS, PS or ES algorithms at 256, 384 or 512 bits, and refuses none and HMAC. It picks up the IdP's key rotation without any change on your side.

Each connection's page shows the values you give the IdP:

Value
Sign-in URLhttps://console.parity.ninja/sso/<sign-in name>
Redirect URIhttps://console.parity.ninja/auth/sso/callback
SCIM URLhttps://api.parity.ninja/scim/v2

Sign in

People choose Sign in with SSO on the console's sign-in page and enter the organization's sign-in name. They can also go straight to the sign-in URL: bookmark it, or set it as the app tile's URL in the IdP. Parity does not route people to an IdP by their email domain.

A person who signs in through SSO is a member of that organization only. The email their IdP sends is shown in Parity and recorded on their tokens, but it is never treated as a verified email: it cannot accept another organization's invite or create an organization.

A console session lasts 12 hours. Parity checks the IdP only at sign-in; removal through SCIM ends a session sooner (see Offboarding).

Access

AccessWho gets in
Anyone the IdP signs inAnyone the IdP authenticates for the Parity app. Parity creates their membership at their first sign-in, with the default role. Control who gets in by assigning the app in the IdP.
Only people provisioned by SCIMOnly people the IdP has provisioned to Parity through SCIM and not deactivated. Anyone else is refused.

Under either setting, someone the IdP has deactivated through SCIM is refused.

SCIM provisioning

SCIM 2.0 lets the IdP push people and groups to Parity as they change. Point the IdP at the SCIM URL and give it a SCIM token.

  • Tokens. Under SCIM on the connection's page, choose Create token. The token is shown once. Revoke tokens from the same list, which shows when each was last used. Several tokens can be live at once, so you can rotate without a gap: create the new one, update the IdP, then revoke the old one. Removing the connection revokes all its tokens.
  • Authentication. The IdP sends the token as Authorization: Bearer <token>.
Supported
ResourcesUsers, Groups, ServiceProviderConfig, ResourceTypes, Schemas
OperationsGET, POST, PUT, PATCH, DELETE on Users and Groups. No bulk, sort, ETag or password change.
Filterseq, joined by and, on userName, externalId, emails, displayName, id, active and group members. Value filters such as emails[type eq "work"] and members[value eq "<id>"]. Anything else is refused with 400 invalidFilter.
PaginationstartIndex and count; default 100, at most 1,000 per page
PATCHadd, replace and remove, in any letter case. Ops with or without a path; paths with value filters (emails[type eq "work"].value, created when missing) and extension URNs; "True"/"False" strings for booleans; removing members by value list or filter. A group PATCH answers 204.
GroupsexcludedAttributes=members on reads. Members must be users already pushed to Parity.
Other attributesKept and returned as sent. id, meta and schemas are set by Parity.
Body sizeUp to 8 MB per request, enough for a group of about 50,000 members

Provisioning someone records them in the organization's directory. They become a member when they first sign in.

How a sign-in finds its provisioned user

When someone signs in, Parity matches them to a provisioned user in this order:

  1. Their earlier sign-in through this connection.
  2. The IdP's user ID: SCIM externalId equal to the ID token's sub, or to Entra ID's oid.
  3. Their email, against the SCIM userName or email. An email the ID token marks email_verified: false never matches.

Roles from groups

Map IdP groups to roles on the Groups page in the account menu. Select groups, choose Set role, and pick Viewer, Developer, Admin or None.

  • Where groups come from. From SCIM, or from the groups claim in the ID token at each sign-in. The From column says which. Groups from the claim count only for people SCIM doesn't manage.
  • Effective role. A member's role is the higher of their own role and the roles of their mapped groups. Parity re-reads it within seconds of a change, so mapping or unmapping a group needs no new sign-in.
  • Limits. You can't map a group to a role above your own. A sign-in records at most 500 groups from the claim.
  • Admins. A group can grant Admin, but an organization always keeps at least one Admin assigned directly who signs in with GitHub or Google, so the IdP can never remove every Admin.
  • Deleted groups. A group the IdP deletes stays on the list, marked deleted, while a role still points at it. Its members lose that role.

The Members page shows each member's effective role, the groups it comes from, and the IdP that manages them.

Add people from the directory

Once the IdP has a directory in Parity, through SCIM or sign-ins, the invite dialog on Members becomes Add people. It searches the IdP's people and groups as you type, and still takes guest emails for people outside the IdP.

  • Picking a group maps it to the role you choose, at once.
  • Picking a person sets the role their membership starts with. Someone who has already signed in gets it immediately.
  • Guests by email get an ordinary invite and sign in with GitHub or Google.

Offboarding

What happensEffect
The IdP deactivates or deletes a user through SCIMTheir membership ends at once, the project tokens created for them are revoked, and their console session fails its next check.
You remove a SCIM-managed member in ParityRefused with 409 managed_by_idp. Unassign them from the Parity app in the IdP.
You remove a connectionEvery membership it created ends and its SCIM tokens stop working. Members who sign in with GitHub or Google stay.
Without SCIM, the IdP removes someoneThey can't sign in again, but a session already open lasts up to 12 hours. Remove them on Members to end it now. Under Anyone the IdP signs in, a member removed only in Parity rejoins at their next sign-in, so unassign them in the IdP too.

The organization's last Admin who signs in with GitHub or Google can't be removed or demoted, so deprovisioning through the IdP never leaves the organization without an Admin.

Okta

Not yet tested against Okta

These steps follow Okta's documentation. Parity's SSO and SCIM are tested end to end against Keycloak, and against Okta's documented SCIM requests, but not yet against a live Okta org.

  1. In the Okta Admin Console, under Applications, create an app integration with the sign-in method OIDC - OpenID Connect and the application type Web Application.
  2. Add Parity's redirect URI under Sign-in redirect URIs. To start sign-in from the Okta dashboard, set the initiate login URI to the connection's sign-in URL.
  3. Copy the client ID and client secret from the General tab. The issuer is your Okta org URL, such as https://acme.okta.com.
  4. For groups in the token: on the Sign On tab, edit OpenID Connect ID Token, set the groups claim type to Filter, name the claim groups, and choose a filter such as Matches regex .*.
  5. Assign people or groups on the Assignments tab.
  6. For SCIM: on the General tab, edit App Settings and set Provisioning to SCIM. On the Provisioning tab, under Integration:
    • SCIM connector base URL: the SCIM URL.
    • Unique identifier field for users: userName.
    • Supported provisioning actions: Push New Users, Push Profile Updates and Push Groups.
    • Authentication Mode: HTTP Header, with the SCIM token as the bearer token.
  7. Under To App, enable creating users, updating user attributes and deactivating users. Push the groups you want to map on the Push Groups tab.

Okta doesn't offer SCIM on OIDC integrations created in its Classic experience. In that case, keep the OIDC app for sign-in and add a separate SWA app integration for provisioning only, assigned to the same people. Both carry the same Okta user ID, so sign-ins still link.

Okta's sub is the Okta user ID, which Okta also sends as the SCIM externalId, so a sign-in links straight to its provisioned user.

Microsoft Entra ID

Not yet tested against Entra ID

These steps follow Microsoft's documentation. Parity's SSO and SCIM are tested end to end against Keycloak, and against Entra's documented SCIM requests, but not yet against a live Entra tenant.

  1. In the Microsoft Entra admin center, under Enterprise applications, choose New application, then Create your own application, and pick the non-gallery option. This creates an app registration and an enterprise application that can take provisioning.
  2. In the app registration, add Parity's redirect URI on the Web platform. Under Certificates & secrets, create a client secret and copy its value.
  3. The issuer is https://login.microsoftonline.com/<tenant-id>/v2.0. The client ID is the application (client) ID.
  4. Under Token configuration, add the optional claim email to the ID token. Without it, Parity uses preferred_username when it is an email address.
  5. For SCIM: in the enterprise application, open Provisioning, set Provisioning Mode to Automatic, enter the SCIM URL as Tenant URL and the SCIM token as Secret Token, and choose Test Connection. Assign users and groups under Users and groups, then start provisioning.

Prefer SCIM for groups. Entra's groups claim carries group object IDs, not names, so groups from the claim appear on the Groups page as IDs. A user in too many groups gets a link instead of groups in the token ("groups overage"); Parity then keeps their previous groups. If you use the claim, send only Groups assigned to the application.

Entra provisions on a cycle, roughly every 40 minutes, so new people and group changes reach Parity with that delay.

Entra's sub differs per application. Parity links a sign-in to its provisioned user by Entra's oid when SCIM externalId holds the object ID, and otherwise by email.

Troubleshooting

These are the messages people see when sign-in fails. <IdP> is the connection's Name.

MessageCauseFix
No organization signs in as <name>. Check the name with your IT team.No connection has that sign-in name.Use the sign-in name from the connection's page.
<IdP> didn't answer. Try again, or ask an Admin to check the SSO settings.Parity couldn't read the issuer's discovery document.Check the issuer on the connection.
This sign-in expired or didn't start here. Sign in again.More than 10 minutes passed, or the IdP sent the person straight to the redirect URI without Parity starting the sign-in.Start at the sign-in URL. Point app tiles at the sign-in URL, not the redirect URI.
<IdP> didn't sign you in (<error>). If you should have access, ask your IT team to assign you the Parity app.The IdP refused, often because the person isn't assigned the app.Assign the person or their group in the IdP.
Sign-in with <IdP> failed. Try again, or ask an Admin to check the SSO settings.The code exchange or ID token check failed: wrong client secret, unregistered redirect URI, or a token for another client.Check client ID, client secret and redirect URI against the IdP.
Your <IdP> account isn't assigned to Parity yet. Ask your IT team to assign you the Parity app.Access is Only people provisioned by SCIM and no provisioned user matches.Assign the person in the IdP and wait for provisioning.
Your <IdP> account is deactivated for Parity. Ask your IT team.The IdP deactivated the user through SCIM.Reactivate them in the IdP.
Another <IdP> account already signed in to Parity as <email>. Ask an Admin to check the org's directory.A different IdP user ID already signed in with this email. Parity refuses rather than merge them.Check the IdP for a reused or renamed account.
This organization's SSO was removed. Ask an Admin.The connection was removed during sign-in.Reconnect the IdP, or sign in with GitHub or Google.
Your account has no access to this organization. Ask an Admin.The membership was refused after sign-in.Check the person on Members.

SCIM errors go back to the IdP in SCIM's error format. An unknown or revoked token gets 401 with a message to create a new one under Sign-in.

API

Setting up SSO needs sso.manage, which only Admins have and agents never do. Searching the directory and mapping groups need members.manage. Agents can't change group or directory roles. All endpoints act on the organization of the calling token.

GET/v0/org/ssosso.manage

Lists the connections, each with sign_in_url, redirect_uri, scim_url and its live scim_tokens. The client secret is never returned.

POST/v0/org/ssosso.manage
Request
{
  "name": "Okta",
  "slug": "acme",
  "issuer": "https://acme.okta.com",
  "client_id": "0oa1b2c3d4",
  "client_secret": "…",
  "groups_claim": "groups",
  "access": "jit",
  "default_role": "Viewer"
}

access is jit (anyone the IdP signs in) or scim (only provisioned people). groups_claim, access and default_role are optional, with the defaults above. Returns 201 with the connection, after checking the issuer's discovery document.

PATCH/v0/org/sso/{connection}sso.manage

Changes the fields sent. An empty client_secret keeps the stored one. A changed issuer is checked again.

DELETE/v0/org/sso/{connection}sso.manage

Removes the connection, revokes its SCIM tokens and ends the memberships it created. Returns memberships_ended.

POST/v0/org/sso/{connection}/testsso.manage

Checks the issuer's discovery document. The full check is a sign-in at the sign-in URL.

POST/v0/org/sso/{connection}/scim-tokenssso.manage

Returns 201 with token, shown once, its metadata as scim_token, and scim_url.

DELETE/v0/org/sso/{connection}/scim-tokens/{token}sso.manage

Revokes a SCIM token by its id. Returns 204.

GET/v0/org/directory?q=members.manage

Searches the directory's people and groups by name or email. limit is 1 to 50, default 20. Returns an empty list when no IdP is connected.

PUT/v0/org/directory/users/{user}/rolemembers.manage

Sets the role a directory user starts with, {"role": "Developer"}. If they have already signed in, their membership changes at once.

GET/v0/org/groupsmembers.manage

Lists groups with their mapped role and active member count. Takes q, limit (1 to 200, default 50) and cursor from the previous page's next.

PUT/v0/org/groups/{group}/rolemembers.manage

Maps a group to a role, {"role": "Developer"}. {"role": ""} removes the mapping. A role above your own is refused with 403.