decolua/9router · error
Missing SAMLResponse parameter in assertion POST body
Error message
Missing SAMLResponse parameter in assertion POST body
What it means
The SAML Web Browser SSO POST binding requires the IdP to send the base64-encoded assertion in a SAMLResponse form field. validateSamlResponse normalizes the body (wrapping non-object bodies as {SAMLResponse: body}) and throws when container.SAMLResponse is absent or empty, since there is nothing to decode, parse, or signature-verify.
Source
Thrown at src/lib/auth/saml.js:145
* @param {Request} request
* @param {object} body - Parsed form body or object containing SAMLResponse
* @param {string} expectedRequestId - Request ID stored in saml_state cookie
* @param {object} settings
* @returns {Promise<object>}
*/
export async function validateSamlResponse(request, body, expectedRequestId, settings) {
if (!settings?.samlCert) {
throw new Error("IdP X.509 Certificate (samlCert) is missing or not configured");
}
const origin = getSamlBaseUrl(request, settings);
const samlInstance = createSamlInstance(settings, origin);
const container = typeof body === "object" && body !== null ? body : { SAMLResponse: body };
const rawSamlResponse = container.SAMLResponse;
if (!rawSamlResponse) {
throw new Error("Missing SAMLResponse parameter in assertion POST body");
}
// Parse response XML to inspect InResponseTo for replay protection
if (expectedRequestId) {
const xml = Buffer.from(rawSamlResponse, "base64").toString("utf8");
const match = xml.match(/InResponseTo=["']([^"']+)["']/i);
const inResponseTo = match ? match[1] : null;
if (!inResponseTo || inResponseTo !== expectedRequestId) {
throw new Error(`InResponseTo mismatch: expected ${expectedRequestId}, received ${inResponseTo || "none"}`);
}
}
const result = await samlInstance.validatePostResponseAsync({ SAMLResponse: rawSamlResponse });
const profile = result?.profile || result;
return profile;
}View on GitHub (pinned to 90b52e06ff)
Solutions
- Confirm the IdP Assertion Consumer Service (ACS) URL points at this endpoint so the assertion POST actually arrives here.
- Ensure the route parses application/x-www-form-urlencoded bodies (e.g. req.formData() or the app's body parser) and passes the parsed object to validateSamlResponse.
- Test by logging Object.keys(body) right before the call to verify SAMLResponse is present and non-empty.
- If the SPA relays the assertion, forward req body as form-encoded (SAMLResponse=<...>&RelayState=<...>) rather than JSON.
Example fix
// before
await validateSamlResponse(req, {}, stateId, settings); // no SAMLResponse
// after
const form = await req.formData();
const body = Object.fromEntries(form.entries()); // { SAMLResponse: '...', RelayState: '...' }
await validateSamlResponse(req, body, stateId, settings); Defensive patterns
Strategy: validation
Validate before calling
function hasSamlResponse(body) {
const container = typeof body === 'object' && body !== null ? body : { SAMLResponse: body };
return typeof container.SAMLResponse === 'string' && container.SAMLResponse.trim() !== '';
}
if (!hasSamlResponse(body)) return res.status(400).json({ error: 'SAMLResponse missing from POST body' }); Type guard
function hasSamlResponseField(b) {
return !!b && typeof b === 'object' && typeof b.SAMLResponse === 'string' && b.SAMLResponse.length > 0;
} Try / catch
try {
const profile = await validateSamlResponse(req, body, stateId, settings);
} catch (err) {
if (String(err.message).includes('Missing SAMLResponse')) {
return res.status(400).json({ error: 'Bad SAML callback: no SAMLResponse field in POST body' });
}
throw err;
} Prevention
- Verify the IdP ACS URL matches this callback route exactly so the assertion POST lands here.
- Ensure the route parses form-urlencoded bodies before reading SAMLResponse (the POST binding is form-encoded, not JSON).
- Don't let middleware (body-size limits, raw-body capture, CSRF filters) consume or drop the POST body before the handler runs.
- Log Object.keys(body) at the callback entry during integration testing to catch empty/misparsed bodies early.
When it happens
Trigger: POSTing to the SAML callback with a body lacking a SAMLResponse field (empty form post, JSON without the key), body being an empty string, or the body parser consuming the stream before the handler reads it so SAMLResponse is undefined.
Common situations: The SPA sent a fetch/axios POST with JSON headers so the form-urlencoded IdP fields were not parsed; the callback URL was visited directly (GET/no body) during testing; a misconfigured IdP RelayState/ACS posted to the wrong endpoint; middleware (body limit, CSRF, raw-body capture) stripped or consumed the multipart/urlencoded body.
Related errors
- IdP X.509 Certificate (samlCert) is missing or not configure
- InResponseTo mismatch: expected ${expectedRequestId}, receiv
- Vertex partner models require a project_id. Add it in provid
- Vertex: failed to mint access token from Service Account JSO
- Vertex: failed to refresh access token from ADC JSON (author
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/246da5d2e408bb4c.
Report an issue: GitHub.