Broken Authentication in Express with Bearer Tokens
Broken Authentication in Express with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Broken Authentication in Express when using Bearer Tokens typically arises from a combination of weak token handling, missing validation, and improper storage or transmission practices. When an API relies on Bearer Tokens without enforcing strict token lifecycle and transport controls, attackers can hijack or forge credentials to assume identities.
One common pattern is an Express route that reads req.headers.authorization and assumes a Bearer token is present and valid without verifying its signature, expiration, or scope. For example:
app.get('/profile', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}
const token = authHeader.split(' ')[1];
// Vulnerable: no verification of token signature or claims
res.json({ profile: 'user data' });
});
If the token is not cryptographically verified, an attacker can craft a valid-looking token or replay a stolen token. Without token binding or audience/issuer validation, the server cannot distinguish legitimate tokens from tampered ones.
Another vulnerability vector is token leakage in logs, URLs, or browser storage. For instance, passing tokens as URL query parameters exposes them in server logs and browser history:
GET /api/data?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
This practice increases the risk of token exposure through referrer headers or log aggregation systems. Additionally, failing to set the HttpOnly, Secure, and SameSite attributes on cookies that store tokens (if used in a cookie-based flow) can enable cross-site scripting (XSS) token theft.
Insecure token storage on the client side, such as storing tokens in localStorage, makes them accessible to JavaScript and susceptible to XSS. Without short expiration times and refresh token rotation, compromised tokens remain valid for extended periods, amplifying the impact of token leakage.
Broken Authentication in this context is not limited to missing signature checks. It also encompasses missing rate limiting on token validation endpoints, lack of proper token revocation mechanisms, and missing detection of anomalous token usage patterns. When combined with weak entropy in token generation, the attack surface grows significantly, enabling token prediction or brute-force attacks.
Finally, missing or misconfigured CORS policies can allow unauthorized origins to include bearer tokens in requests, leading to cross-origin credential leakage. Together, these factors make the Express + Bearer Token combination prone to session hijacking, privilege escalation, and unauthorized access when security controls are incomplete or inconsistently applied.
Bearer Tokens-Specific Remediation in Express — concrete code fixes
Remediation focuses on cryptographically verifying tokens, enforcing transport security, and minimizing exposure. Use a well-audited library such as jsonwebtoken to validate tokens, and enforce strict checks on issuer, audience, and expiration.
Here is a secure Express route example using Bearer Tokens with JWT verification:
const jwt = require('jsonwebtoken');
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`;
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'], audience: 'api.example.com', issuer: 'https://auth.example.com/' }, (err, decoded) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.user = decoded;
next();
});
}
app.get('/profile', authenticateToken, (req, res) => {
res.json({ profile: 'user data', user: req.user.sub });
});
Always specify the algorithm (e.g., RS256) to prevent algorithm confusion attacks. Validate the aud and iss claims to ensure the token was issued for your API and by a trusted identity provider.
Enforce HTTPS in production by using app.use((req, res, next) => { if (req.secure) next(); else res.status(400).send('HTTPS required'); }); or a load balancer-level redirect. Set short access token lifetimes (e.g., 15 minutes) and implement refresh token rotation with strict storage rules.
On the client side, store tokens in memory or in secure, HttpOnly cookies with Secure and SameSite=Strict flags rather than localStorage. If cookies are used, ensure CSRF protections are in place. Avoid URL parameters for tokens to prevent leakage in logs and browser history.
Add rate limiting on authentication endpoints to mitigate brute-force and token guessing attacks. Libraries such as express-rate-limit can be applied to sensitive routes:
const rateLimit = require('express-rate-limit');
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: { error: 'Too many attempts, try again later' },
});
app.post('/login', authLimiter, (req, res) => {
// handle credentials and issue tokens
});
Implement token revocation via a denylist (short-lived) or by rotating signing keys and maintaining a minimal validity window. Monitor for anomalous patterns such as multiple regions or IPs using the same token, and respond with token invalidation when suspicious activity is detected.
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 |