AuthnRequest
What Is an AuthnRequest?
When a user wants to SSO into a SAML SP, the SP sends an AuthnRequest to the IdP asking the IdP to authenticate the user. It's essentially a signed message saying "please authenticate this user and send the assertion here."
Initiate Endpoint
GET /saml/initiate?client_id=test-client&redirect_uri=https://app.example.com/callbackThe SP:
- Validates
client_idandredirect_uriagainst the OAuth client registry - Generates a signed
AuthnRequestXML document - Base64URL-encodes it as
SAMLRequest - Redirects to the IdP SSO URL with
SAMLRequestandRelayStatequery parameters
The AuthnRequest XML
<?xml version="1.0" encoding="UTF-8"?>
<samlp:AuthnRequest
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_abc123"
IssueInstant="2026-04-10T12:00:00Z"
Destination="https://idp.example.com/sso"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
AssertionConsumerServiceURL="http://localhost:8080/saml/acs"
>
<saml:Issuer>http://localhost:8080/saml/sp</saml:Issuer>
<samlp:NameIDPolicy
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
AllowCreate="true"/>
</samlp:AuthnRequest>Key attributes:
ID: Unique identifier for this request. Stored in-memory with a 5-minute TTL for replay protection. When the IdP returns the assertion, it includesInResponseTo="$ID".IssueInstant: Timestamp. IdPs will reject requests that are too old.Destination: The IdP's SSO URL. Configured viasaml.idp.sso-url.AssertionConsumerServiceURL: Where the IdP should POST the assertion back. Must exactly match the SP's ACS URL.NameIDPolicy: Tells the IdP what format of NameID to return.unspecifiedis the most permissive.
How It's Signed
The entire AuthnRequest XML is signed using JSR 105 (JDK XML Digital Signature) with RSA-SHA256:
// SamlAuthnRequestService.java
XMLSignature signature = XMLSignatureFactory.getInstance("DOM")
.newXMLSignature(signedInfo, keyInfo);
// Sign using SP private key from keystore
signature.sign(new DOMSignContext(spPrivateKey, document.getDocumentElement()));The signature is embedded as a <ds:Signature> element inside the AuthnRequest.
Replay Protection
After generating the AuthnRequest, the SP stores the request ID:
// In-memory store with 5-minute TTL
Map<String, Instant> pendingRequests = new ConcurrentHashMap<>();
pendingRequests.put(requestId, Instant.now());When the IdP returns the SAMLResponse with InResponseTo="$ID", the SP checks that $ID exists in pendingRequests and has not expired. This prevents attackers from replaying old assertions.
Redirect to IdP
The signed AuthnRequest XML is Base64URL-encoded and appended to the IdP SSO URL:
https://idp.example.com/sso?
SAMLRequest=Base64URL(SignedAuthnRequestXML)&
RelayState=Base64URL({"client_id":"test-client","redirect_uri":"https://app.example.com/callback"})The RelayState is a JSON object containing the OAuth client_id and redirect_uri, which the SP needs later to complete the OIDC token issuance. It's Base64URL-encoded to survive the redirect.