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

  1. Confirm the IdP Assertion Consumer Service (ACS) URL points at this endpoint so the assertion POST actually arrives here.
  2. 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.
  3. Test by logging Object.keys(body) right before the call to verify SAMLResponse is present and non-empty.
  4. 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

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


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/246da5d2e408bb4c. Report an issue: GitHub.