Home

Documentation

Documentation

REST API Requirements (SAML)

What your REST API must do to accept access tokens whose identity came from a SAML assertion.

Token validation is protocol-agnostic

Signature verification, iss/aud/exp checks, scope enforcement, CORS and the health endpoint are identical to the OIDC path, because the access token is an ordinary RFC 9068 JWT either way. Rather than repeat them, this page covers the one thing that differs: resolving the user. For the multi-language JWT middleware, CORS configuration and health-endpoint requirements, see REST API Setup (OIDC). All of it applies unchanged.


Checklist

  1. Accept Authorization: Bearer <token> on protected endpoints
  2. Validate the JWT signature against the Auth Server's JWKS
  3. Verify iss, aud, and exp
  4. Enforce scopes per endpoint
  5. Resolve the user from sub_id, not sub ← the SAML-specific step
  6. Expose a /health endpoint returning HTTP 200
  7. Allow CORS from the playground origins, or use Proxy Mode

Steps 1–4, 6 and 7 are covered in REST API Setup (OIDC). Step 5 is below.


Token claims

The access token your server receives:

ClaimValue
isshttps://auth.resource.xaa.dev
audYour resource server URL, exactly as registered (trailing slash preserved as-is)
subAn opaque AS-local user id. With the playground AS, {provider}:saml-user-<uuid>. Not a durable key, see below
sub_idThe structured SAML subject: { format: "saml-nameid", issuer, nameid, … }, forwarded unchanged from the ID-JAG. This is your user key.
emailPresent only when the NameID is an email address. See email is conditional
scopeSpace-separated granted scopes
app_orgProvider (tenant) name that authenticated the user
expExpiry (Unix timestamp)

The header uses typ: at+jwt (RFC 9068).

`sub` is present, but it is not the user's identity

The AS mints sub itself, so it is collision-free, and keying on it will not merge two tenants. But it carries no enterprise identity and is not stable: the playground generates it per process, so it changes when the AS restarts and any rows keyed against it are orphaned. Resolve on sub_id; treat sub as an opaque request-scoped handle.


Resolving the user

After your existing middleware has validated the token, map sub_id to a local user on the composite identity (issuer, nameid, sp_name_qualifier when present).

Four details that are easy to get wrong:

  • Compare the qualifier with NULL-safe equality, not =. A plain = never matches a NULL qualifier, so every request from an IdP that omits it provisions a fresh duplicate. Use IS NOT DISTINCT FROM (PostgreSQL, SQLite), <=> (MySQL), or branch on null explicitly.
  • Pick one representation for "no qualifier", either NULL or a sentinel, and use it on both write and read. Two representations coexisting is the same duplicate-user bug by another route. A unique index over a nullable column also does not prevent duplicates on most databases (details).
  • Make JIT provisioning idempotent. Two concurrent first requests both miss the lookup; without an upsert, one fails or you get two rows.
  • Reject a malformed sub_id rather than falling through to the sub branch.

email is conditional

email is set only when the NameID is an email address, meaning nameid_format is absent or names an email format. A persistent or transient NameID arrives with no email claim, which is why every sample above falls back to nameid. Never make it a required column, and never key on it.


Next step

Ready to test? See Testing Guide to run the five-step SAML flow against your server, or Own Auth Server if you issue the access tokens yourself.

These materials and any recommendations within are not legal, privacy, security, compliance, or business advice. These materials are intended for general informational purposes only and may not reflect the most current security, privacy, and legal developments nor all relevant issues. You are responsible for obtaining legal, security, privacy, compliance, or business advice from your own lawyer or other professional advisor and should not rely on the recommendations herein.

Presented byOkta Developer

Copyright © 2026 Okta. All rights reserved.