Single sign-on (SSO) lets your users open Grid Orchestrator from your own portal or identity provider, signed in, without a Grid password.
Grid supports two methods.
- HMAC-signed launch: your server signs a short form, and the browser posts it to Grid.
- SAML 2.0: your identity provider (Okta, Microsoft Entra ID, Google Workspace, and others) posts a signed assertion to Grid.
Both methods end the same way. Grid redirects the browser to Orchestrator with a one-time code, Orchestrator redeems it, and the user lands signed in. Grid never puts a bearer token in a URL.
Choosing a method
| HMAC-signed launch | SAML 2.0 | |
|---|---|---|
| Who starts sign-in | Your application, from a link or button you control | Your identity provider, from its app dashboard |
| What Grid trusts | A signing key you generate in Grid | Your identity provider's signing certificate |
| Best when | You already authenticate users in your own portal | Your users already sign in through a corporate IdP |
| Role for new users | You choose, per request | You choose, per request |
| Return to your portal | return_to, signed with the request | RelayState, which must match a saved portal origin |
| Setup lives in | Your server code | Your IdP's application settings |
You can enable both. Configure each one independently under the same parent customer.
Sessions, users, and roles
Every SSO sign-in names a target customer: your parent customer or one of its direct sub-organizations. For HMAC it is resource_id; for SAML it is the customerId in the ACS URL. Trust (the signing key or IdP certificate) always comes from the parent, even when the target is a sub-organization.
Grid matches the signed-in email against Orchestrator users:
| Situation | What happens |
|---|---|
| The email belongs to a user on the target customer | That user's own session opens. |
| The email belongs to a parent user, and the target is a sub-org | The parent user opens the sub-organization in view-as mode, if they are allowed to view it. |
| The email is new to Grid | Grid creates the user on the target customer, then opens their session. |
| The email belongs to a user on a different customer | Sign-in is rejected (SSO_USER_CUSTOMER_MISMATCH). A sub-org user cannot open the parent. |
Roles for new users. With HMAC, role sets the new user's role. If you omit it, the default is Case Reviewer. Use a role name exactly as it appears in Orchestrator, for example Observer, Processor, Reviewer, or Case Reviewer. Admin and Billing are rejected. Existing users keep their current role: role and name fields apply only when Grid creates a user.
HMAC-signed launch
1. Create your signing key
In Developer → SSO, select Create Key. Grid shows the full key once. Store it in your secrets manager. Later views show only a truncated value.
Keep the key on your server. Anyone with the key can sign in as any email on your customer. Never ship it to a browser or mobile app. If it leaks, select Rotate Key: the old key stops working immediately.
2. Build the request
Post these fields to POST {API host}/auth/sso/launch, as a form (application/x-www-form-urlencoded) or as JSON.
| Field | Required | Signed | Notes |
|---|---|---|---|
resource_id | Yes | Yes | Target customer: your parent customer ID, or a direct sub-organization id. |
timestamp | Yes | Yes | Unix time in seconds, as a string. Must be within 120 seconds of Grid's clock. |
email | Yes | Yes | The user to sign in. Signed exactly as posted. |
token | Yes | No | The signature (see below). |
role | No | If sent | Role for a newly created user. |
return_to | No | If sent | Portal URL to return to after sign-in. |
name[firstName], name[lastName] | No | No | Name for a newly created user. In JSON, send name: { firstName, lastName }. |
3. Sign it
Join these values with a colon (:), in this order, then compute HMAC-SHA256 with your signing key. Send the digest in token as 64 lowercase hex characters.
resource_idtimestampemailrole, only if you sendroleencodeURIComponent(return_to), only if you sendreturn_to
# No role, no return_to
3f8a2c1e-5b7d-4e9a-9c2f-1a2b3c4d5e6f:1792536400:[email protected]
# With role
3f8a2c1e-5b7d-4e9a-9c2f-1a2b3c4d5e6f:1792536400:[email protected]:Observer
# With role and return_to (https://portal.example.com/grid)
3f8a2c1e-5b7d-4e9a-9c2f-1a2b3c4d5e6f:1792536400:[email protected]:Observer:https%3A%2F%2Fportal.example.com%2FgridUse the key exactly as Grid shows it. The signing key is a 64-character string. Pass that string to HMAC as-is (UTF-8). Do not hex-decode it.
import { createHmac } from 'node:crypto'
export function signGridLaunch({ signingKey, resourceId, email, role, returnTo }) {
const timestamp = String(Math.floor(Date.now() / 1000))
const parts = [resourceId, timestamp, email]
if (role) parts.push(role)
if (returnTo) parts.push(encodeURIComponent(returnTo))
const token = createHmac('sha256', signingKey).update(parts.join(':')).digest('hex')
return {
resource_id: resourceId,
timestamp,
email,
token,
...(role ? { role } : {}),
...(returnTo ? { return_to: returnTo } : {}),
}
}import hashlib
import hmac
import time
from urllib.parse import quote
def sign_grid_launch(signing_key, resource_id, email, role=None, return_to=None):
timestamp = str(int(time.time()))
parts = [resource_id, timestamp, email]
if role:
parts.append(role)
if return_to:
# Matches JavaScript's encodeURIComponent.
parts.append(quote(return_to, safe="-_.!~*'()"))
token = hmac.new(signing_key.encode(), ":".join(parts).encode(), hashlib.sha256).hexdigest()
fields = {"resource_id": resource_id, "timestamp": timestamp, "email": email, "token": token}
if role:
fields["role"] = role
if return_to:
fields["return_to"] = return_to
return fields4. Send the browser
The launch must be a top-level browser navigation, because Grid answers with a redirect that the browser follows. Render the signed fields into a form that submits itself, in the current tab or a new window. Do not call the endpoint from your server or with fetch.
<form id="grid-sso" method="post" action="https://api.gridverify.com/auth/sso/launch">
<input type="hidden" name="resource_id" value="{{resource_id}}" />
<input type="hidden" name="timestamp" value="{{timestamp}}" />
<input type="hidden" name="email" value="{{email}}" />
<input type="hidden" name="token" value="{{token}}" />
<input type="hidden" name="name[firstName]" value="{{first_name}}" />
<input type="hidden" name="name[lastName]" value="{{last_name}}" />
</form>
<script>document.getElementById('grid-sso').submit()</script>Sign immediately before rendering the form. Each request is valid for 120 seconds and can be used once. A refreshed or re-submitted page needs a new signature.
Returning to your portal
When you send return_toGrid sends the browser back to that URL after the session is stored. Grid keeps only the origin and path (query and fragment are dropped). Because return_to is part of the signature, nobody can change it in transit.
SAML 2.0
Grid supports IdP-initiated SAML 2.0 with the HTTP-POST binding: users start from your identity provider's dashboard. Service-provider-initiated sign-in (starting from a Grid page) is not supported.
1. Configure Grid
In Developer → SSO, open the SAML configuration and enter:
- IdP entity ID: the issuer your identity provider puts in its assertions.
- IdP signing certificates: one to three PEM certificates. Adding a second lets you rotate without downtime (see below).
- Portal origins (optional):
httpsorigins Grid may send users back toRelayState, up to 10.
2. Configure your identity provider
Create a SAML application in your IdP with these values. You can import them from Grid's metadata instead: GET {API host}/auth/sso/saml/{customerId}/metadata.
| Setting | Value |
|---|---|
| ACS URL (Single sign-on URL) | {API host}/auth/sso/saml/{customerId}/acs |
| Audience (SP entity ID) | urn:grid:saml:{parentCustomerId} |
| Binding | HTTP-POST |
| Signing | Sign the assertion (signing the outer response is optional) |
| NameID | Email address format |
| Attributes | email, firstName, lastName, role |
The audience is always the parent customer ID. The ACS URL uses the target customer ID. To sign users into a sub-organization, create a separate IdP application whose ACS URL uses that sub-organization's ID: download its metadata from /auth/sso/saml/{subOrganizationId}/metadata.
What Grid Checks
- The issuer matches your saved IdP entity ID.
- The assertion is signed by one of your saved certificates.
- The audience is
urn:grid:saml:{parentCustomerId}. - The response
Destinationand the assertionRecipientequal the exact ACS URL that received it. - The assertion is inside its validity window (2 minutes of clock skew allowed) and no more than 5 minutes old.
- The assertion ID has not been used before.
Certificate rotation
Before your IdP switches signing certificates, add the new certificate next to the current one. Both are accepted. After the switch, remove the old certificate. Grid shows saved certificates as SHA-256 fingerprints, never the certificate itself.
Returning to your portal
To send users back to your portal after sign-in, set RelayState to the portal URL in your IdP (often called "Default RelayState"), and add that URL's origin under Portal origins. A RelayState whose origin is not saved is ignored, and the user lands in Orchestrator.
After sign-in
Both methods redirect the browser to Orchestrator:
{Orchestrator host}/auth/callback?code={one-time code}&method=ssoOrchestrator redeems the code (single use, valid for 2 minutes), stores the session, and opens the user's landing page, or your return_to / RelayState URL. You do not call any other endpoint.
Errors
When sign-in fails, Grid still redirects, with an error code instead of a one-time code. Orchestrator shows the user a sign-in error.
{Orchestrator host}/auth/callback?method=sso&error={CODE}| Code | Method | Cause and fix |
|---|---|---|
SSO_LAUNCH_FIELD_REQUIRED | Both | A required field is missing, or the SAML assertion has no email. |
SSO_LAUNCH_TIMESTAMP_INVALID | HMAC | timestamp is not a number or is more than 120 seconds from Grid's clock. Sync your server clock (NTP). |
SSO_SIGNATURE_INVALID | Both | HMAC: the canonical string or key is wrong. SAML: issuer, certificate, audience, Destination, Recipient, or timing failed. |
SSO_LAUNCH_REPLAYED | Both | The token or assertion was already used. Sign a fresh request. |
SSO_SIGNING_KEY_NOT_FOUND | HMAC | The parent customer has no signing key. Create one in Developer → SSO. |
SSO_SAML_CONFIGURATION_NOT_FOUND | SAML | The parent customer has no SAML configuration. |
SSO_MODULE_NOT_ENABLED | Both | SSO is not enabled for the parent customer. |
CUSTOMER_NOT_FOUND | Both | resource_id or the ACS customerId does not exist. |
SSO_USER_CUSTOMER_MISMATCH | Both | The email belongs to a user on a different customer. |
SSO_USER_NOT_ORCHESTRATOR | Both | The email belongs to a non-Orchestrator account (for example, an applicant). |
SSO_ROLE_NOT_ASSIGNABLE | HMAC | role is Admin or Billing. |
SSO_ROLE_NOT_FOUND | HMAC | role is not a Grid role name. |
FORBIDDEN | Both | A parent user is not allowed to view the target sub-organization. |
Security checklist
- Keep the HMAC signing key in a secrets manager, sign on your server only, and rotate it if it may have been exposed.
- Keep server clocks synced. Reject launches outside the 120-second window.
- Sign each launch just before the browser sends it; never cache or reuse signed forms.
- Keep Portal origins to the exact
httpsorigins you own. - Store your IdP's next certificate before it rotates, and remove retired certificates.
- Remove users in Grid when they leave. SSO creates users, but it does not deactivate them.