Insufficient Logging in Adonisjs with Saml
Insufficient Logging in Adonisjs with Saml
When integrating Security Assertion Markup Language (SAML) into an AdonisJS application, insufficient logging can prevent detection of authentication and authorization failures, protocol misuse, and account compromise. AdonisJS does not provide built-in SAML-specific log instrumentation; developers must explicitly instrument their SAML event handlers and strategy integrations. Without structured logs for SAML flows, critical events such as AuthN requests, Logout requests, NameID policy violations, and signature validation failures are not reliably recorded. This gap is especially important because SAML relies on redirects and signed assertions, where tampering or malformed assertions may not surface as application errors without explicit logging.
Inadequate logging becomes a security risk when stack traces or generic HTTP error responses expose implementation details to clients while failing to capture the underlying SAML context required for forensic analysis. For example, if an invalid Signature or an expired NotOnOrAfter condition causes an unhandled exception, and the handler logs only a generic 500 error, defenders lose visibility into whether the incident involved a replay attack, a clock-skew-based denial, or a misconfigured Identity Provider (IdP). MiddleBrick’s scans highlight gaps in observability by correlating runtime behavior with missing logging signals across its 12 checks, including Input Validation and Authentication.
AdonisJS typically consumes SAML via community packages (e.g., adonisjs-saml) where you configure entry points, certificates, and callbacks. The framework’s request lifecycle means that without explicit logging inside the SAML consumer route or service, you will not retain an auditable trail of assertions, NameID formats, or session indices. Consider a scenario where an attacker crafts a SAMLResponse with an elevated NameID to escalate privileges; if your route handler does not log the incoming NameID, issuer, and assertion ID, you cannot reconstruct the sequence that led to the privilege escalation. Effective logging must capture protocol-level metadata (InResponseTo, IssueInstant, Destination), assertion-level identifiers (Issuer, SubjectConfirmationData), and outcome (success, validation failure, signature mismatch) while avoiding logging sensitive assertion content or PII.
Operational practices matter: ensure logs include correlation identifiers that tie a SAML AuthN request to its callback, making it possible to trace a single authentication flow across redirects. In AdonisJS, this can be implemented by generating a request-scoped correlation ID early in the middleware chain and propagating it into the SAML handler’s logging context. Combine this with structured logging (e.g., JSON lines) so fields like protocol version, binding, and certificate thumbprint are queryable. When logs are insufficient, teams struggle to answer basic questions: Was the failure due to clock skew? Was the certificate rotated without updating the SP metadata? Did a logout request succeed? MiddleBrick’s findings around Authentication and Input Validation underscore the need for precise, actionable log data to support incident response and compliance evidence.
Saml-Specific Remediation in Adonisjs — Concrete Code Fixes
Remediation centers on instrumenting SAML event handlers with structured, context-rich logs and hardening validation to ensure every critical step is recorded. Below are concrete patterns for AdonisJS using a typical SAML package (replace package and imports with the exact library you use).
1. Structured Logging in SAML Callback
Log essential SAML fields at the point of assertion validation. Use a correlation ID to tie requests across redirects.
// start of request middleware to set correlation ID
const { v4: uuidv4 } = require('uuid');
const correlationId = uuidv4();
// In your SAML callback route handler
async samlCallback({ request, response, session }) {
const samlResponse = request.post('SAMLResponse');
const inResponseTo = request.post('RelayState') || 'unknown-relay';
const issuer = request.get('issuer'); // or extract from parsed assertion
const now = new Date().toISOString();
// Basic structured log example
console.info(JSON.stringify({
level: 'info',
timestamp: now,
correlationId,
event: 'saml_callback',
inResponseTo,
issuer,
sessionId: session.id,
message: 'SAML Response received'
}));
try {
const profile = await saml.verify(samlResponse);
console.info(JSON.stringify({
level: 'info',
timestamp: new Date().toISOString(),
correlationId,
event: 'saml_verification_success',
nameId: profile.nameId,
nameIdFormat: profile.nameIdFormat,
issuer: profile.issuer,
sessionIndex: profile.sessionIndex
}));
// proceed with login
} catch (error) {
console.warn(JSON.stringify({
level: 'warn',
timestamp: new Date().toISOString(),
correlationId,
event: 'saml_verification_failure',
error: error.message,
code: error.code || 'UNKNOWN'
}));
// handle failure
}
}
2. Logging Signature and Validity Details
Record signature validation outcomes and NotBefore/NotOnOrAfter checks to detect time-based or certificate-related issues.
function validateAndLog(samlResponse, options) {
const correlationId = options.correlationId;
// Assume saml.parseResponse returns parsed assertion with validation flags
const parsed = saml.parseResponse(samlResponse);
console.info(JSON.stringify({
level: 'debug',
timestamp: new Date().toISOString(),
correlationId,
event: 'saml_signature_and_validity',
signatureValid: parsed.signatureValid,
notBefore: parsed.notBefore,
notOnOrAfter: parsed.notOnOrAfter,
currentTime: new Date().toISOString(),
issuer: parsed.issuer,
destination: parsed.destination
}));
if (!parsed.signatureValid) {
console.error(JSON.stringify({
level: 'error',
timestamp: new Date().toISOString(),
correlationId,
event: 'saml_signature_invalid',
reason: 'Signature validation failed',
fingerprint: options.certFingerprint
}));
throw new Error('Invalid signature');
}
if (new Date() < new Date(parsed.notBefore) || new Date() >= new Date(parsed.notOnOrAfter)) {
console.error(JSON.stringify({
level: 'error',
timestamp: new Date().toISOString(),
correlationId,
event: 'saml_validity_window_error',
notBefore: parsed.notBefore,
notOnOrAfter: parsed.notOnOrAfter
}));
throw new Error('Validity window error');
}
}
3. Instrumenting Logout and Single Logout (SLO)
Log SLO requests and responses to detect token reuse or unexpected logout behavior.
async sloCallback({ request, response }) {
const correlationId = request.$try('param', 'correlationId') || uuidv4();
const samlRequest = request.query.get('SAMLRequest');
console.info(JSON.stringify({
level: 'info',
timestamp: new Date().toISOString(),
correlationId,
event: 'saml_slo_request',
samlRequestLength: samlRequest ? samlRequest.length : 0
}));
try {
const result = saml.handleLogoutRequest(samlRequest);
console.info(JSON.stringify({
level: 'info',
timestamp: new Date().toISOString(),
correlationId,
event: 'saml_slo_success',
sessionIndex: result.sessionIndex
}));
response.redirect(result.redirectUrl);
} catch (error) {
console.error(JSON.stringify({
level: 'error',
timestamp: new Date().toISOString(),
correlationId,
event: 'saml_slo_failure',
error: error.message
}));
response.serverError();
}
}
4. Avoid Logging Sensitive Content
Never log raw assertions or NameID values that may contain PII. If you need identifiers for correlation, hash or truncate deliberately.
const safeNameId = profile.nameId ? profile.nameId.substring(0, 8) + '****' : 'none';
console.info(JSON.stringify({
level: 'info',
timestamp: new Date().toISOString(),
correlationId,
event: 'saml_user_authenticated',
safeNameId
}));