HIGH broken authenticationexpresshmac signatures

Broken Authentication in Express with Hmac Signatures

Broken Authentication in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability

HMAC signatures are commonly used in Express APIs to verify the integrity and origin of a request. When implemented incorrectly, this pattern introduces Broken Authentication, which is cataloged in the OWASP API Top 10. A canonical example is accepting an HMAC signature without also validating the request method, path, and a strict timestamp or nonce, enabling replay attacks and signature misuse.

Consider an Express route that verifies an HMAC but does not bind the signature to the request body and a shared secret in a canonical way. An attacker who observes a valid signed request can replay it to the same endpoint, and if the server does not enforce idempotency checks or a short validity window, authentication is effectively bypassed. This becomes particularly dangerous when the endpoint changes state (for example, transferring funds or updating permissions) because the server treats the replayed, correctly signed request as legitimate.

Another common pitfall is leaking the shared secret through logs, error messages, or client-side code. If the secret is discoverable, an attacker can generate valid HMAC signatures for arbitrary paths, breaking authentication entirely. Insecure default configurations, such as accepting unsigned requests for backward compatibility, also weaken the scheme. Additionally, failing to normalize query parameter ordering leads to signature inconsistency; the same logical request signed on the client may not match the server’s canonical form, causing the server to either accept malformed input or reveal behavior differences that aid an attacker.

Middleware that only checks the presence of a signature header, without verifying the full canonical string, is another root cause. For instance, if the signature is computed over a subset of headers or body fields, an attacker can tamper with unchecked elements (such as content-type or custom headers) and still produce a valid-looking signature. Without strict schema validation and cryptographic binding of all relevant parts of the request, the authentication provided by HMAC is superficial.

Timing attacks further exacerbate the problem. If the signature comparison uses a standard equality check instead of a constant-time comparison, an attacker can infer information about the correct signature byte-by-byte. This can lead to authentication bypass or secret recovery, especially in unauthenticated attack surfaces where an endpoint is exposed without prior credentials. The combination of weak canonicalization, missing replay protection, and insecure comparison turns HMAC-based authentication in Express into a vulnerable pattern that closely aligns with Broken Authentication findings in automated scans.

Hmac Signatures-Specific Remediation in Express — concrete code fixes

To remediate Broken Authentication when using HMAC signatures in Express, you must enforce a strict, canonical representation of the request, validate a timestamp or nonce, and use constant-time comparison. Below are concrete, working examples that demonstrate a secure implementation.

First, define a canonical string builder that includes the HTTP method, the request path, a timestamp, and the raw request body. This ensures that the same logical request always produces the same signature, preventing method tampering and replay across time windows.

const crypto = require('crypto');

function buildCanonicalString(method, path, timestamp, body) {
  return `${method.toUpperCase()}\n${path}\n${timestamp}\n${body}`;
}

Next, create a signing function that uses a shared secret and SHA256 to produce the HMAC. Store the secret outside the source code, for example in environment variables, and never log it.

function computeHmac(secret, message) {
  return crypto.createHmac('sha256', secret).update(message).digest('hex');
}

On the server side, implement middleware that verifies the signature, checks the timestamp freshness, and uses a constant-time comparison. This middleware should run before the route handler to reject tampered or stale requests.

const crypto = require('crypto');

const SHARED_SECRET = process.env.HMAC_SHARED_SECRET;
const TOLERANCE_SECONDS = 30;

function verifyHmac(req, res, next) {
  const receivedSignature = req.headers['x-hmac-signature'];
  const timestamp = req.headers['x-request-timestamp'];
  const body = req.is('application/json') ? JSON.stringify(req.body) : (req.rawBody || '');

  if (!receivedSignature || !timestamp) {
    return res.status(401).json({ error: 'Missing signature or timestamp' });
  }

  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp, 10)) > TOLERANCE_SECONDS) {
    return res.status(401).json({ error: 'Request expired' });
  }

  const canonical = buildCanonicalString(req.method, req.path, timestamp, body);
  const expectedSignature = computeHmac(SHARED_SECRET, canonical);

  const isValid = crypto.timingSafeEqual(
    Buffer.from(receivedSignature, 'hex'),
    Buffer.from(expectedSignature, 'hex')
 );

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  next();
}

Apply this middleware to routes that require HMAC authentication. For GET queries, ensure that the query string is normalized (e.g., keys sorted lexicographically) before building the canonical string, so that equivalent requests with different parameter orders are treated identically.

app.post('/api/transfer', verifyHmac, (req, res) => {
  // Business logic here; signature and timestamp have already been validated
  res.json({ status: 'ok' });
});

Additionally, include a nonce or one-time-use cache (such as a short-lived Redis set) to bind each signature+timestamp combination to a single use, effectively preventing replay attacks even if the timestamp window is generous. With these concrete fixes in place, the HMAC-based authentication in Express aligns with secure design patterns and reduces the likelihood of a Broken Authentication finding.

Related CWEs: authentication

CWE IDNameSeverity
CWE-287Improper Authentication CRITICAL
CWE-306Missing Authentication for Critical Function CRITICAL
CWE-307Brute Force HIGH
CWE-308Single-Factor Authentication MEDIUM
CWE-309Use of Password System for Primary Authentication MEDIUM
CWE-347Improper Verification of Cryptographic Signature HIGH
CWE-384Session Fixation HIGH
CWE-521Weak Password Requirements MEDIUM
CWE-613Insufficient Session Expiration MEDIUM
CWE-640Weak Password Recovery HIGH

Frequently Asked Questions

Why does HMAC authentication in Express still pose authentication risks?
Risks arise from missing canonicalization of method, path, timestamp, and body; lack of replay protection; absence of constant-time comparison; and insecure storage or exposure of the shared secret, enabling signature replay or forgery.
How can replay attacks be prevented for HMAC-signed Express endpoints?
Include a timestamp with a short tolerance and a server-side nonce cache (e.g., Redis) to ensure each signed request is used only once, and always bind the signature to the full request body and exact path.