Integration
Verify the JWT.
Simple SAML delivers every successful sign-in as an RS256 JWT, posted to your destination's callback URL. This page is the contract: claim shape, signing key, and verification recipes.
The delivery
After a successful sign-in, Simple SAML renders
a self-submitting HTML form whose
action is
your destination's callback URL. The form has a
single field:
token=<jwt>
Method is
POST,
content type is
application/x-www-form-urlencoded
.
Existing query parameters on the callback URL
are preserved.
The signing key
Tokens are signed with RSA-PKCS1 v1.5 over
SHA-256 (algorithm
RS256).
The public key is published as a JWKS at:
https://sso.simplesaml.com/.well-known/jwks.json
The endpoint sets a one-hour
Cache-Control
header. Any standards-compliant JWT library will
cache the JWKS for you, match the token's
kid
header against a key in the set, and refresh on
rotation. There is no shared secret to store.
The claims
Every JWT carries the same seven claims:
- sub
- SAML NameID of the authenticated user
- iss
- Always https://sso.simplesaml.com
- aud
- Your destination's token
- src
- The source token that authenticated the user
- iat
- Issued-at, Unix seconds
- exp
- Expires-at. Always iat + 300 (five minutes)
- jti
- Unique JWT ID for replay defense
When the source has attribute passthrough enabled (Standard plan and above), every SAML attribute statement is also merged into the payload. The seven standard claims are reserved and can never be overwritten. Multi-valued attributes become JSON arrays; single-valued attributes become strings.
The checks that matter
A correct verification confirms all of:
-
Signature against the JWKS, with
kidandalgfrom the token header. -
issequalshttps://sso.simplesaml.com. -
audequals your destination's token. -
exphas not passed. -
Optionally:
jtihas not been seen before, if you keep a session table. The form post itself shouldn't be replayable.
Tokens are intentionally short-lived. The five-minute TTL is fixed for every destination; there is no per-destination override. Exchange them immediately for an application session; don't store or forward them.
Node
With jose:
import { jwtVerify, createRemoteJWKSet } from "jose";
const jwks = createRemoteJWKSet(new URL(
"https://sso.simplesaml.com/.well-known/jwks.json",
));
const { payload } = await jwtVerify(token, jwks, {
algorithms: ["RS256"],
issuer: "https://sso.simplesaml.com",
audience: "<your-destination-token>",
}); Python
With PyJWT:
import jwt
from jwt import PyJWKClient
jwks = PyJWKClient(
"https://sso.simplesaml.com/.well-known/jwks.json",
)
signing_key = jwks.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key,
algorithms=["RS256"],
issuer="https://sso.simplesaml.com",
audience="<your-destination-token>",
) Ruby
With the
jwt
gem, on Rails (the
jwks:
option takes a loader you provide, so cache it
through
Rails.cache):
require "jwt"
require "net/http"
JWKS_URL = URI(
"https://sso.simplesaml.com/.well-known/jwks.json",
)
jwks_loader = ->(opts) {
if opts[:kid_not_found]
Rails.cache.delete("simplesaml/jwks")
end
Rails.cache.fetch("simplesaml/jwks", expires_in: 1.hour) do
JSON.parse(
Net::HTTP.get(JWKS_URL),
symbolize_names: true,
)
end
}
payload, = JWT.decode(
token,
nil,
true,
algorithms: ["RS256"],
jwks: jwks_loader,
iss: "https://sso.simplesaml.com",
aud: "<your-destination-token>",
verify_iss: true,
verify_aud: true,
) What goes wrong
Failures on the verification side almost always fall into one of three buckets:
- Audience mismatch. You're verifying with the wrong destination token. Each destination has its own token; a JWT minted for destination A will not validate for destination B.
- Clock skew. Your server's clock is more than a couple of minutes off, and tokens look expired or not-yet-valid. Use NTP.
- Stale JWKS cache. Very rare, but if you cache the JWKS in your own code rather than letting the library handle it, a key rotation can leave your cache pinned to a key that's no longer in the set. Let the library cache it.
Failures on the SAML side, before a JWT ever gets minted, show up on the user's screen as Authentication failed. The error message names the specific failure (signature, audience, expiry, replay).