Volley / Templates / SAML single sign-on
Sign-in templateSAML single sign-on sequence diagram
SP-initiated SAML 2.0 sign-in with the decoded AuthnRequest, the self-submitting form, the post to your ACS URL and every check the assertion has to pass.
Click any arrow in the editor to see its request and response as a table or the raw body. Change the hosts, ids and fields to match your integration, then share a link or embed it in your README.
Step by step
- The app sees no session and redirects to the identity provider with a deflated, base64
SAMLRequestand aRelayState. - The user signs in at the identity provider, with MFA if it requires it.
- The identity provider returns a form that posts itself to your Assertion Consumer Service URL, carrying the signed
SAMLResponse. - Your app validates the assertion: signature, issuer, audience, destination,
InResponseToand the time window. - Your app starts a session and redirects to the
RelayState, or answers 403.
Easy to get wrong
- Check that the assertion itself is signed, not only the outer response, unless your library handles both.
- Store the request id and reject assertions whose
InResponseToyou did not issue, to stop replays. - Session cookies set on the cross-site POST need
SameSitesettings that allow it, or set the cookie on the redirect that follows.
The source
The whole diagram is this plain text. Copy it into your repo, or open it in the editor.
saml-sso.volley
title: SAML 2.0 single sign-on, started by the app
autonumber
order: User, Browser, App, Identity Provider
User -> Browser: Open app.example.com/dashboard
Browser -> App: GET /dashboard
note App: No session, so the app starts SAML sign-in
App --> Browser: 302 to the identity provider
out: Redirect
Location: https://idp.example.com/sso/saml?SAMLRequest=fZJNT8MwDIb%2FSuV7m6Zs&RelayState=%2Fdashboard
Browser -> Identity Provider: GET /sso/saml
in: AuthnRequest (deflated and base64 encoded in the query, decoded here)
```text
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_a1b2c3d4" Version="2.0" IssueInstant="2026-10-16T14:00:00Z"
AssertionConsumerServiceURL="https://app.example.com/saml/acs">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://app.example.com/saml/metadata</saml:Issuer>
</samlp:AuthnRequest>
```
Identity Provider --> Browser: Sign-in page
Browser -> Identity Provider: POST /login
in: Credentials
username: ada@example.com
password: <redacted>
...: {fas-user-lock} Multi-factor check, if the IdP requires it
Identity Provider --> Browser: 200 OK, a form that posts itself
out: Auto-submitting form
```text
<form method="post" action="https://app.example.com/saml/acs">
<input type="hidden" name="SAMLResponse" value="PHNhbWxwOlJlc3BvbnNlIHhtbG5z">
<input type="hidden" name="RelayState" value="/dashboard">
</form>
<script>document.forms[0].submit()</script>
```
Browser -> App: POST /saml/acs
in: Form
SAMLResponse: PHNhbWxwOlJlc3BvbnNlIHhtbG5z
RelayState: /dashboard
App -> App: Validate the assertion
in: Assertion (decoded)
Issuer: https://idp.example.com
NameID: ada@example.com
Audience: https://app.example.com/saml/metadata
Destination: https://app.example.com/saml/acs
InResponseTo: _a1b2c3d4
NotOnOrAfter: 2026-10-16T14:05:00Z
groups: engineering
note: Check the signature against the IdP certificate, then the audience, destination, `InResponseTo` and time window. A replayed or unsigned assertion must fail.
if: Assertion is valid
App --> Browser: 302 to /dashboard
out: Response headers
Location: /dashboard
Set-Cookie: session=<redacted>; HttpOnly; Secure; SameSite=Lax
else: Invalid, expired or not for this app
App --> Browser: 403 Forbidden
end