Insecure Design with Bearer Tokens
How Insecure Design Manifests in Bearer Tokens
Insecure design in bearer tokens typically occurs when security controls are implemented incorrectly or missing entirely, rather than through implementation bugs. The most common manifestation is when tokens are designed to be long-lived without proper rotation mechanisms, creating extended attack windows if compromised. For example, a JWT token with a 30-day expiration that's stored in localStorage becomes a persistent vulnerability - if an attacker obtains it through XSS, they maintain access for weeks.
Another critical design flaw is using predictable token generation patterns. Consider this vulnerable implementation:
function generateToken(userId) {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(7);
return `${userId}.${timestamp}.${random}`;
}This approach is fundamentally insecure because the token structure reveals the user ID and timestamp, making it trivial for attackers to guess valid tokens for other users. The random component uses Math.random(), which is cryptographically weak and predictable.
Missing or weak token binding represents another design failure. Without binding tokens to specific client characteristics (IP address, user agent, device fingerprint), stolen tokens can be used from anywhere. This becomes especially dangerous when combined with missing revocation mechanisms - once issued, tokens remain valid indefinitely unless they expire naturally.
Scope creep in token permissions is a subtle but dangerous design issue. A token intended for read-only access that accidentally includes write permissions creates an attack surface that's difficult to mitigate once deployed. This often happens when developers copy-paste token generation code without reviewing the claims and permissions carefully.
Finally, insecure design manifests in how tokens are transmitted and stored. Sending tokens in URLs (GET parameters) instead of headers exposes them in browser history, server logs, and referrer headers. Storing tokens in cookies without the HttpOnly flag enables JavaScript-based theft through XSS attacks.
Bearer Tokens-Specific Detection
Detecting insecure design in bearer tokens requires both static analysis of the implementation and dynamic testing of the runtime behavior. Start by examining the token generation and validation code for predictable patterns, weak entropy sources, and missing security controls.
For JWT tokens, verify the signing algorithm is not 'none' and uses strong algorithms like RS256 or ES256. Check that tokens include proper claims like 'exp' (expiration), 'iat' (issued at), and 'nbf' (not before). Here's a detection script that analyzes JWT claims:
function analyzeJwtClaims(token) {
try {
const decoded = jwt.decode(token, { complete: true });
const claims = decoded.payload;
const issues = [];
if (!claims.exp || claims.exp - Math.floor(Date.now()/1000) > 86400) {
issues.push('Token lifetime exceeds 24 hours');
}
if (!claims.iat) {
issues.push('Missing issued-at timestamp');
}
if (!claims.sub || !claims.iss) {
issues.push('Missing subject or issuer claims');
}
return issues;
} catch (error) {
return ['Invalid JWT format'];
}
}Automated scanning tools like middleBrick can detect these design flaws by analyzing the token validation logic and runtime behavior. The scanner tests for missing rate limiting on token endpoints, predictable token patterns, and improper claim validation.
Dynamic testing should include attempting to use expired tokens to verify proper rejection, testing with modified claims to check validation robustness, and attempting replay attacks across different IP addresses or user agents to test token binding.
middleBrick specifically scans for insecure token design patterns by examining the API's authentication flow, testing token expiration handling, and verifying that tokens are properly scoped and bound to client characteristics. The scanner can identify when tokens are transmitted insecurely or when the token validation logic contains design flaws that could be exploited.
Bearer Tokens-Specific Remediation
Remediating insecure design in bearer tokens requires architectural changes rather than simple code fixes. The foundation is implementing proper token lifecycle management with short expiration times (5-15 minutes for access tokens) and refresh token rotation.
// Secure token generation with proper claims and rotation
function createSecureToken(payload, userId) {
const accessToken = jwt.sign(
{
...payload,
userId,
type: 'access',
exp: Math.floor(Date.now()/1000) + 900 // 15 minutes
},
process.env.JWT_SECRET,
{ algorithm: 'RS256' }
);
const refreshToken = jwt.sign(
{
userId,
type: 'refresh',
version: getUserTokenVersion(userId),
exp: Math.floor(Date.now()/1000) + 86400 // 24 hours
},
process.env.REFRESH_SECRET,
{ algorithm: 'RS256' }
);
return { accessToken, refreshToken };
}Implement token binding by including client-specific information and validating it on each request. This prevents token theft from one device being used on another:
function bindTokenToClient(token, req) {
const clientInfo = {
ip: req.ip,
userAgent: req.headers['user-agent'],
deviceId: req.deviceId || generateDeviceId()
};
const boundToken = jwt.sign(
{
...token,
client: clientInfo,
exp: Math.floor(Date.now()/1000) + 900
},
process.env.JWT_SECRET,
{ algorithm: 'RS256' }
);
return boundToken;
}Store tokens securely using HttpOnly cookies for refresh tokens and Authorization headers for access tokens. Never store tokens in localStorage or transmit them in URLs:
// Secure token storage and transmission
app.post('/login', (req, res) => {
const { accessToken, refreshToken } = createSecureToken(
{ username: req.body.username },
req.user.id
);
res.cookie('refresh_token', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/refresh',
maxAge: 86400000
});
res.json({ accessToken });
});Implement proper token revocation by maintaining a token blacklist or using token versioning. When a user logs out or their permissions change, invalidate all existing tokens:
// Token revocation using versioning
function logout(userId) {
incrementUserTokenVersion(userId);
// All existing tokens with old version are now invalid
}Finally, implement comprehensive logging and monitoring for token-related events. Track token generation, usage patterns, and revocation events to detect anomalies that might indicate token abuse or compromise.