Skip to content

SAML applications

Sign people in to SAML 2.0 applications from the catalog or from their metadata, with your directory as the identity provider.

Updated View as Markdown

A saml application is a SAML 2.0 service provider, such as Slack, Zoom or your own app, that signs your directory’s people in with your directory as its identity provider (IdP). Only users you assign to the application, directly or through a group, can sign in to it.

SAML applications count against the same limit as other applications: up to 200 per directory. You need sso.applications.create.

Create a SAML application

Start from one of three places:

  • The catalog: a common SaaS application with its settings filled in. You enter the values that are yours, such as your Slack domain. See The catalog.
  • The service provider’s metadata: a metadata URL or the XML document. EZGH SSO imports the entity ID, the HTTP-POST ACS URLs, the single logout URL and the certificates from it, once. It doesn’t refresh them later.
  • The details: the service provider’s entity ID and ACS URLs.
  1. Open EZGH SSO > Applications and select Create application.
  2. Choose SAML application.
  3. Under Start from, choose From the catalog, From the app’s SAML metadata or Enter the details.
  4. Fill in the details, review, and select Create application.

The application opens on its Sign-in tab, with what to give the service provider.

# From the catalog
ezgh sso applications create --catalog slack --param slackDomain=acme.slack.com

# From the service provider's metadata
ezgh sso applications create --name Grafana --kind saml --metadata-url https://grafana.acme.com/saml/metadata

# From the details
ezgh sso applications create --name Tool --entity-id https://tool.acme.com --acs-url https://tool.acme.com/saml/acs \
  --attribute 'email=${user.email}' --attribute 'groups=${user.groups}'

Flags given with --catalog or metadata win over what they fill in.

Call CreateApplication with kind: "saml":

{
  "name": "Grafana",
  "kind": "saml",
  "saml": { "metadataUrl": "https://grafana.acme.com/saml/metadata" }
}

Or give the settings themselves:

{
  "name": "Tool",
  "kind": "saml",
  "saml": {
    "entityId": "https://tool.acme.com",
    "acsUrls": ["https://tool.acme.com/saml/acs"],
    "attributes": [{ "name": "email", "value": "${user.email}" }]
  }
}

metadataUrl must be a public https URL; it’s fetched at once, and a failed fetch is 400. metadataXml takes the document itself, up to 256 KiB. Fields sent alongside either win over what they import.

Then assign users or groups, and register the IdP at the service provider.

Register the IdP at the service provider

Each SAML application has its own IdP: its own entity ID and metadata, with your directory’s signing certificate. So you can add the same service provider twice. saml.idp in the application, the console’s Sign-in tab and ezgh sso applications idp show:

Value Form
IdP entity ID, metadata URL https://<alias>.sso.ezgh.app/saml/metadata/<application ID>
SSO URL (HTTP-Redirect and HTTP-POST) https://<alias>.sso.ezgh.app/saml/sso/<application ID>
SLO URL (HTTP-Redirect and HTTP-POST) https://<alias>.sso.ezgh.app/saml/slo/<application ID>
IdP-initiated sign-in https://<alias>.sso.ezgh.app/saml/sso/<application ID>, opened without a request
Signing certificates The directory’s certificates. See Certificates.

Service providers that import IdP metadata take the metadata URL. Others take the entity ID, the SSO URL and the certificate:

ezgh sso applications idp Grafana
ezgh sso applications idp-certificate Grafana > ezgh-sso.pem

In the console, select Download certificate on the Sign-in tab.

If you change your directory’s alias, these URLs change: update the service provider.

Settings

saml holds the service provider’s settings. On update, only the fields you send change; metadataUrl or metadataXml imports metadata again.

Field Description Default
entityId The service provider’s entity ID, 1 to 1,024 characters: the assertion’s audience, and the Issuer its requests must carry. Required
acsUrls 1 to 10 exact ACS URLs for the HTTP-POST binding: https, or http on a loopback address. The first is the default. Required
sloUrl, sloBinding The service provider’s single logout URL, and its binding: HTTP-Redirect or HTTP-POST. null, HTTP-Redirect
nameIdFormat email, persistent or transient. See NameID formats. email
attributes Up to 50 attributes. See Attributes. None
groupsFilter Which groups ${user.groups} lists: assigned (the user’s groups assigned to this application) or all. assigned
signResponse, signAssertion Sign the Response, the Assertion, or both. At least one. Both true
encryptAssertions, encryptionAlgorithm Encrypt the assertion to the service provider’s encryption certificate, with aes128-gcm or aes256-cbc. false, aes128-gcm
spCertificates Up to 10 of the service provider’s certificates, { "use": "signing" | "encryption", "certificate" }: base64 DER or PEM, RSA (2048 bits or more) or ECDSA. Encryption needs RSA. None
idpInitiated Allow IdP-initiated sign-in, and the portal tile. true
defaultRelayState The RelayState sent with IdP-initiated sign-in: where the service provider lands. Up to 1,024 characters. null

The answer also shows metadataUrl and metadataImportedAt (where and when metadata was last imported) and idp.

NameID formats

nameIdFormat NameID sent Format URI
email The user’s email address urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
persistent The user’s ID, which never changes urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
transient A new value for every assertion urn:oasis:names:tc:SAML:2.0:nameid-format:transient

A request whose NameIDPolicy asks for a format other than the application’s (or unspecified) gets an InvalidNameIDPolicy error response.

Attributes

Each attribute is { "name", "nameFormat", "value" }. name is what the service provider expects, up to 256 characters, and each name appears once. nameFormat is unspecified (the default), uri or basic. value is a template of up to 1,024 characters:

Variable Value
${user.id} The user’s ID
${user.email} The email address
${user.name} The display name
${user.givenName}, ${user.familyName} The given and family names
${user.department} The department: set on the user, or sent by inbound SCIM
${user.groups} Exactly this and nothing else: one value per group name, filtered by groupsFilter

Literal text and variables can be mixed, such as ${user.givenName} ${user.familyName}. An attribute whose value is a single variable the user has no value for is left out.

ezgh sso applications update Tool --attribute 'email=${user.email}' --attribute 'department=${user.department}' \
  --attribute 'groups=${user.groups}' --groups-filter all

Signing and encryption

Responses and assertions are signed with RSA-SHA256 and exclusive canonicalization. By default both the Response and the Assertion are signed, which most service providers accept; turn one off only if the service provider needs it.

With encryptAssertions, the assertion is signed, then encrypted to the service provider’s encryption certificate: the content with AES-128-GCM (default) or AES-256-CBC, the key with RSA-OAEP. Add the certificate to spCertificates with use: "encryption", or import it with the metadata.

ezgh sso applications update Slack --encrypt-assertions --encryption-certificate slack-encryption.pem

Certificates

Each directory has one SAML signing key: RSA-3072, with a self-signed certificate valid for three years. It’s made when you create the directory’s first SAML application, and every SAML application uses it.

status Meaning
active Signs responses and assertions now.
next The successor, made 90 days before the active certificate expires and published at once in every application’s metadata and saml.idp.certificates. It starts signing when the active certificate expires (activatesAt), and the old one is then removed.

Each certificate shows its fingerprintSha256, subject, notBefore, notAfter and warnAt, 60 days before it expires, from when the console warns you.

Rollover: service providers that read your IdP metadata URL pick up the next certificate on their own. For the others, give them the next certificate once it’s published and before the active one expires:

ezgh sso applications idp-certificate Slack --next > ezgh-sso-next.pem

You can’t roll the certificate early.

How sign-in works

Service provider-initiated: the service provider sends an AuthnRequest to the SSO URL, with the HTTP-Redirect or HTTP-POST binding. EZGH SSO checks that:

  • its Issuer is the application’s entityId;
  • its Destination, when sent, is the SSO URL, and it was issued within the last 90 seconds;
  • the ACS it names, by URL or index, is one of acsUrls (without one, the default is used);
  • its RelayState is at most 1,024 bytes, and the request is at most 64 KiB.

A request that fails these checks gets an error page; nothing is sent to an ACS URL. A valid request continues with the person’s portal session, or they sign in first. With ForceAuthn, they sign in again unless they signed in after the request was made. With IsPassive, someone who would have to sign in gets a NoPassive error response instead. A ProtocolBinding other than HTTP-POST gets UnsupportedBinding.

IdP-initiated: opening the SSO URL without a request (the portal tile does this) signs the person in to the default ACS URL, with defaultRelayState. It’s refused when idpInitiated is off. A RelayState in the URL is never used.

Portal tiles: assigned users see a tile for each SAML application that allows IdP-initiated sign-in. The tile can show the application’s own logo.

Either way, the person must be assigned, checked at every sign-in: someone who isn’t sees an error page (with IsPassive, a RequestDenied response), recorded in Trails as a failed AuthorizeApplication.

The assertion

Part Value
Issuer The IdP entity ID
Subject The NameID in the application’s format, with a bearer confirmation for the ACS URL, valid for 5 minutes
Conditions AudienceRestriction with the application’s entityId; NotBefore one minute before issue
AuthnStatement AuthnInstant (when the person signed in), SessionIndex (the portal session), SessionNotOnOrAfter (when the session ends)
AuthnContextClassRef https://refeds.org/profile/mfa for a multi-factor sign-in, urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport for a password alone, otherwise urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
AttributeStatement The application’s attributes

Single logout

Single logout is best effort, for the browser the request comes from. A LogoutRequest to the SLO URL (HTTP-Redirect or HTTP-POST), checked like an AuthnRequest, ends that browser’s portal session when it names the session (SessionIndex) or the user. That also ends the session’s OIDC grants without offline_access, with their back-channel logout notices. The signed LogoutResponse goes to the application’s sloUrl, or the person sees the signed-out page without one.

Other service providers the person signed in to aren’t told, and EZGH SSO never sends a LogoutRequest itself.

Not supported

  • Signed requests aren’t verified. Signatures on AuthnRequest and LogoutRequest messages are ignored, and spCertificates with use: "signing" are stored but not used. Nothing a request says is trusted beyond what you registered: responses go only to registered ACS URLs, and a forged logout request can at most sign out the browser it’s sent from.
  • No single logout propagation to other service providers, and no IdP-initiated logout.
  • The HTTP-Artifact binding, RequestedAuthnContext (it’s ignored), pairwise persistent NameIDs, and refreshing imported metadata.

The catalog

ListCatalogApplications (ezgh sso catalog list, needs sso.catalog.list) returns 16 common SaaS applications: Slack, Zoom, GitHub Enterprise Cloud (organization), Atlassian Cloud, Salesforce, Google Workspace, AWS IAM Identity Center, Dropbox, Notion, Figma, Zendesk, PagerDuty, Asana, Miro, Sentry and Tailscale. Each entry has:

  • parameters: the values you fill in, such as slackDomain, each with a label, a description, an example and a pattern. They replace {{name}} in the entry’s URLs.
  • saml: SAML settings in this API’s terms (all but Tailscale), with the vendor’s setup steps in notes.
  • oidc: OIDC client settings, where the vendor documents a generic OIDC identity provider (Salesforce, Zendesk and Tailscale), for an OIDC application.
  • scim: a SCIM base URL and attribute mappings for outbound provisioning, where the vendor supports it.
  • sources: the vendor documentation each part was checked against.
ezgh sso catalog get slack

The console and the CLI create the application from an entry’s SAML settings; the API takes the filled-in settings in CreateApplication.

Delete a SAML application

DeleteApplication (ezgh sso applications delete) removes it with its assignments and provisioning. Its users can’t sign in to it through EZGH SSO any more, and its tile leaves the portal.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close