Token Leakage in Express with Hmac Signatures
Token Leakage in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Token leakage occurs when authentication tokens or session identifiers are exposed beyond their intended boundary. In Express applications that use Hmac-based signatures (for example, signing JSON Web Tokens or custom tokens with a shared secret), the combination of insecure logging, improper error handling, and verbose responses can inadvertently disclose signed tokens or the data used to generate them.
Consider an Express route that creates a signed payload using Hmac and returns it to the client:
const crypto = require('crypto');
const express = require('express');
const app = express();
const SHARED_SECRET = process.env.SHARED_SECRET || 'insecure-default-secret';
function signToken(payload) {
const hmac = crypto.createHmac('sha256', SHARED_SECRET);
hmac.update(JSON.stringify(payload));
return hmac.digest('hex');
}
app.get('/token', (req, res) => {
const token = signToken({ sub: 'user-123', iat: Date.now() });
// Risk: returning the raw token in verbose logs or response
console.log('Generated token:', token);
res.json({ token });
});
app.listen(3000);
If server logs, error traces, or API responses expose the token (for instance, through verbose error messages or debugging output), an attacker who can read those logs or responses can capture the Hmac-signed token. This is especially dangerous when the token is used for authorization and is not additionally protected by transport-layer mechanisms or short-lived nonces.
Another leakage vector arises when applications include signed tokens in URLs or query parameters. URLs are often logged in server access logs, browser history, and referrer headers. If the token is embedded in a URL derived from an Hmac signature without additional protections, it can be persistently stored and replayed:
app.get('/resource', (req, res) => {
const token = signToken({ sub: 'user-456', action: 'download' });
const url = `${req.protocol}://${req.get('host')}/download?token=${token}`;
// Risk: token in URL may be logged in access logs or browser history
res.redirect(url);
});
Additionally, insufficient key management (e.g., using a weak or default secret, failing to rotate the secret) weakens the integrity of the Hmac signature. If an attacker discovers the secret, they can forge tokens that the server will accept as valid, effectively bypassing authentication tied to those tokens. The interplay between how tokens are generated, transmitted, and logged in Express routes determines whether Hmac signatures protect or expose the system.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To mitigate token leakage when using Hmac signatures in Express, adopt practices that minimize exposure in logs, URLs, and error messages, and enforce strict key management.
- Avoid logging raw tokens. Instead, log only metadata or hashes of tokens if logging is necessary:
const crypto = require('crypto');
const express = require('express');
const app = express();
const SHARED_SECRET = process.env.SHARED_SECRET;
if (!SHARED_SECRET) throw new Error('Missing SHARED_SECRET');
function signToken(payload) {
const hmac = crypto.createHmac('sha256', SHARED_SECRET);
hmac.update(JSON.stringify(payload));
return hmac.digest('hex');
}
app.get('/token-safe', (req, res) => {
const token = signToken({ sub: 'user-789', iat: Date.now() });
// Safe: do not log the token itself
console.log('Token issued for sub=user-789');
res.json({ token });
});
app.use((err, req, res, next) => {
// Safe: avoid including token in error responses
res.status(500).json({ error: 'Internal server error' });
});
app.listen(3000);
- Prefer transmitting tokens in HTTP headers rather than URLs to prevent leakage in logs and browser history:
app.post('/auth', (req, res) => {
const token = signToken({ sub: 'user-abc', scope: 'read' });
// Transmit via Authorization header pattern: Bearer <token>
res.json({ authorization: `Bearer ${token}` });
});
// Client should then send: Authorization: Bearer <token>
- Enforce strong secrets and key rotation policies. Load secrets from a secure environment and avoid hardcoded defaults:
const SHARED_SECRET = process.env.SHARED_SECRET;
if (!SHARED_SECRET || SHARED_SECRET.length < 32) {
console.warn('Weak or missing secret detected');
// In production, fail startup or load from a secure secret manager
}
- Apply short expiration times and include nonces or timestamps to reduce the impact of token leakage:
function signTokenWithExpiry(payload, expiresInSec = 300) {
const header = { alg: 'HS256', typ: 'JWT' };
const now = Math.floor(Date.now() / 1000);
const payloadWithExp = { ...payload, exp: now + expiresInSec, iat: now };
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
const encodedPayload = Buffer.from(JSON.stringify(payloadWithExp)).toString('base64url');
const message = `${encodedHeader}.${encodedPayload}`;
const signature = crypto.createHmac('sha256', SHARED_SECRET).update(message).digest('base64url');
return `${message}.${signature}`;
}
These measures ensure that Hmac signatures in Express serve their purpose—verifying integrity and authenticity—without becoming a vector for token leakage through logs, URLs, or error handling.