---
title: "Single sign-on with SAML"
---

# Single sign-on with SAML

Connect Microsoft Entra ID, Okta or another SAML 2.0 identity provider to Neural Inverse Govern, test the connection and activate it, with the settings, certificate rotation and session policy.

SAML connections live in Identity Center. Govern computes every service provider value itself, so you copy values from Govern to your identity provider and import the identity provider's metadata back; you never type an entity ID or certificate by hand.

## Before you start

- You need the `sso.connections:*` permissions on the organisation. Owner and Admin hold them.
- In an organisation of several accounts, the Identity Center instance belongs to the management account or a delegated administrator. A member account gets `403 NOT_INSTANCE_ADMINISTRATOR`.
- An organisation can have up to 10 connections.
- The identity provider must serve https endpoints and sign its assertions.

## Set up a connection in the console

Open **Identity Center > Connections** and choose to add a connection. The setup has six steps, and Govern shows the state of each:

1. **Choose the identity provider.** Presets: Microsoft Entra ID, Okta, Google Workspace, Ping Identity, and "Other SAML 2.0 identity provider". The preset fills in the NameID format and attribute names.
2. **Copy the service provider values** to your identity provider (below).
3. **Import the identity provider metadata**, by URL or as XML.
4. **Preview the attribute and group mapping.**
5. **Test sign-in.** You sign in through the identity provider yourself; the result shows the subject, email, attributes, groups and the group mappings they would apply.
6. **Activate** the tested revision.

A connection's configuration is a revision. Editing an active connection writes a draft while the active revision keeps serving, and a draft can only be activated after a passing test sign-in of that exact revision in the last 24 hours.

### Service provider values

| Value | Form |
|---|---|
| Entity ID (identifier, audience URI) | `{origin}/saml/c/{connection_id}` |
| ACS URL (reply URL, single sign-on URL) | `{origin}/saml/c/{connection_id}/acs` |
| Single logout URL | `{origin}/saml/c/{connection_id}/slo` |
| Metadata URL | `{origin}/saml/c/{connection_id}/metadata` |

The metadata at `GET /saml/c/{connection_id}/metadata` is public and holds the entity ID, the ACS (HTTP-POST), single logout in both bindings, and the active and next signing and encryption certificates.

### Microsoft Entra ID

1. In the Microsoft Entra admin center, open **Enterprise applications**, create your own application and choose **SAML single sign-on**.
2. Enter the identifier (entity ID) and the reply URL (ACS URL), or upload the Govern metadata.
3. Copy the **App Federation Metadata URL** from the SAML certificates section and import it in Govern.
4. Keep the default claims and add a group claim for the groups assigned to the application.

The Entra ID preset reads these claims: `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`, `.../givenname`, `.../surname`, `http://schemas.microsoft.com/identity/claims/displayname` and `http://schemas.microsoft.com/ws/2008/06/identity/claims/groups`. An Entra ID group overage (`groups.link`) is refused: configure Entra ID to send only the groups assigned to the application.

### Okta

1. In the Okta Admin Console, create a **SAML 2.0** app integration and enter the single sign-on URL (ACS URL) and the audience URI (entity ID).
2. Copy the metadata URL from the **Sign On** tab and import it in Govern.
3. Add the attribute statements `email`, `firstName` and `lastName`, and a group attribute statement named `groups`.

### Google Workspace and others

Google Workspace maps the primary email to `email`, first and last name to `firstName` and `lastName`, and "Group membership" to the app attribute `groups`. For any other identity provider, release the email attribute, and the name and groups attributes if you use them.

## Settings

| Setting | Default | Meaning |
|---|---|---|
| `IdpEntityId`, `IdpSsoUrl`, `IdpSsoPostUrl`, `IdpSloUrl` | from the metadata | The identity provider's entity ID, sign-in endpoints and logout endpoint. https only. |
| `IdpCertificates` | from the metadata | One or two signing certificates. An expired certificate is refused. |
| `MetadataUrl` | the imported URL | Refreshed daily. Empty for pasted XML. |
| `NameIdFormat` | the preset's | `email_address`, `persistent` or `unspecified`. A transient NameID is refused. |
| `AuthnRequestsSigned` | `true` | AuthnRequests are signed with RSA-SHA256. |
| `UseRedirectBinding` | `true` | HTTP-Redirect; `false` sends an auto-submitted HTTP-POST form. |
| `WantAssertionsSigned` | always `true` | The assertion must be signed itself; a signed response alone is refused. |
| `WantEncryptedAssertions` | `false` | When on, an unencrypted assertion is refused. RSA-OAEP with AES-GCM or AES-CBC is accepted; RSA 1.5 is refused. |
| `SendLoginHint` | `true` for Entra ID, Okta and Ping | Sends the email typed at sign-in as the AuthnRequest subject. |
| `AllowIdpInitiated`, `IdpInitiatedRedirectUrl` | `false`, empty | IdP-initiated sign-in. When on, the response is verified and replay-checked, then handed off to a normal service-provider-initiated sign-in at the redirect URL. |
| `AttributeMap` | the preset's | `Email` (required unless the NameID is an email address), `GivenName`, `FamilyName`, `DisplayName`, `Groups`. |

Every response must carry exactly one assertion. Govern checks the issuer, the audience, the destination, a bearer subject confirmation whose recipient is the ACS URL and whose `InResponseTo` is the pending request, the validity times with two minutes of skew, and an assertion ID replay cache. Documents with a DTD are refused.

## Certificates

Each connection gets a signing and an encryption RSA key pair. Certificates are valid for two years, and the private keys are stored only as Govern Vault ciphertext.

- **Rollover.** The next certificate is published in the metadata 60 days before the active one expires and becomes active 30 days later.
- **Warnings.** Administrators are warned 30, 14, 7 and 1 days before any certificate of a connection expires, the identity provider's included, and when one has expired.
- **Rotate now.** `RotateServiceProviderCertificate` with `Stage: PUBLISH_NEXT` creates and publishes the next key pair; `ACTIVATE_NEXT` activates it early once your identity provider trusts it.

## Sign-out

Signing out of Govern ends the identity provider session with a signed LogoutRequest to `IdpSloUrl`. In the other direction, a signed LogoutRequest from the identity provider to `/saml/c/{connection_id}/slo` (HTTP-Redirect or HTTP-POST) ends the Govern sessions with that SessionIndex and is answered with a signed LogoutResponse. Unsigned or foreign requests are refused with `400` and the reason, and every logout is audited as `auth.saml_logout`. A deployment without the session-ending component answers `503 slo_unavailable` rather than pretending.

## Session policy

Per connection, `PutConnectionSessionPolicy` (`sso.sessions:putconnectionsessionpolicy`) sets:

| Field | Range |
|---|---|
| `MaxSessionMinutes` | 60 to 1440 |
| `IdleTimeoutMinutes` | 15 to 480, at most the maximum |
| `ReauthenticationWindowMinutes` | 5 to 60 |
| `RequireIdpMfaForAdmin` | Owner and Admin actions need an authentication with the listed `amr` or `acr` values |
| `IdpMfaAmrValues`, `IdpMfaAcrValues` | Default `amr` value `mfa` |

The change applies at once to the connection's live sessions. A privileged action in a session whose last identity provider authentication is older than the window is refused with `reauthentication_required`, and the client starts a re-authentication with `StartSessionReauthentication`.

## Refusals

A refused response shows the identity provider failure page and is audited as `auth.external_signin` with its reason, for example `signature_invalid`, `assertion_unsigned`, `idp_certificate_expired`, `audience_mismatch`, `subject_confirmation_invalid`, `assertion_expired`, `encryption_required`, `encryption_algorithm_refused`, `decryption_failed`, `nameid_transient`, `assertion_replayed` or `idp_initiated_disabled`.

Metadata imports are refused with a reason such as `metadata_fetch_failed`, `metadata_malformed`, `metadata_expired`, `metadata_insecure_url`, `metadata_no_signing_certificate` or `metadata_certificate_expired`. A metadata URL is fetched over https with at most 3 redirects and a 15-second limit, and must resolve to a public address.

## API

Identity Center actions are `POST /api/v1/svc/sso/<Action>` with PascalCase JSON. Every change is audited as `sso:<Action>` in the transaction of the change; `sso:UpdateConnection` records the before and after values.

| Action | Permission |
|---|---|
| `CreateConnection` (`Protocol` `saml`, `Preset` `entra_id`, `okta`, `google_workspace`, `ping` or `generic_saml`) | `sso.connections:createconnection` |
| `UpdateConnection` (RFC 7396 merge patch over the draft, or `DiscardDraft`) | `sso.connections:updateconnection` |
| `DescribeConnection`, `ListConnections` | `sso.connections:describeconnection`, `sso.connections:listconnections` |
| `GetConnectionSetup` | `sso.connections:getconnectionsetup` |
| `ImportIdentityProviderMetadata` (`MetadataUrl` or `MetadataXml`, at most 1 MiB) | `sso.connections:importidentityprovidermetadata` |
| `GetServiceProviderMetadata` | `sso.connections:getserviceprovidermetadata` |
| `StartConnectionTest`, `GetConnectionTestResult` | `sso.connections:startconnectiontest`, `sso.connections:getconnectiontestresult` |
| `ActivateConnectionRevision` | `sso.connections:activateconnectionrevision` |
| `RotateServiceProviderCertificate` | `sso.connections:rotateserviceprovidercertificate` |
| `DisableConnection` | `sso.connections:disableconnection` |
| `DeleteConnection` | `sso.connections:deleteconnection` |

`DeleteConnection` ends every session issued through the connection, and is refused with `409 connection_in_use` while a domain points to it in optional or required mode. A test sign-in needs a signed-in administrator; an API key gets `403 TEST_NEEDS_SESSION`.

## Next

- [Domains and SSO enforcement](https://neuralinverse.com/docs/govern/sso-enforcement/) to route people to the connection and make it required.
- [Permission sets and group mappings](https://neuralinverse.com/docs/govern/group-mappings/) to turn identity provider groups into roles.
- [Directory sync with SCIM](https://neuralinverse.com/docs/govern/scim/) to provision and deprovision people.
