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 ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |