Insecure Direct Object Reference with Jwt Tokens
How Insecure Direct Object Reference Manifests in Jwt Tokens
Insecure Direct Object Reference (IDOR) in JWT tokens occurs when attackers manipulate token claims to access unauthorized resources. The stateless nature of JWTs makes them particularly vulnerable to IDOR attacks when developers improperly trust client-side claims without proper validation.
A common JWT IDOR pattern involves the sub (subject) claim. Consider an API endpoint that retrieves user data:
const jwt = require('jsonwebtoken');
// Vulnerable implementation
app.get('/api/user/:id', (req, res) => {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// TRUSTING CLIENT-SIDE CLAIM WITHOUT VALIDATION
const userId = decoded.sub;
// Attacker can modify 'sub' claim to access any user
const userData = db.query('SELECT * FROM users WHERE id = ?', [userId]);
res.json(userData);
});An attacker can easily modify the sub claim using tools like jwt.io or by crafting tokens offline. If they know another user's ID, they can access that user's data simply by changing the sub value in the token.
Another JWT-specific IDOR vulnerability involves role-based escalation through custom claims:
// Vulnerable: trusting client-side role claims
app.get('/api/admin/dashboard', (req, res) => {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Attacker modifies 'role' claim to 'admin'
if (decoded.role === 'admin') {
res.json(adminDashboardData);
} else {
res.status(403).send('Forbidden');
}
});Database IDOR with JWTs is particularly dangerous because the token persists across requests. An attacker can harvest valid tokens from one endpoint and use them to access other endpoints with different authorization requirements:
// Vulnerable: using token data for database queries without ownership validation
app.get('/api/documents/:docId', (req, res) => {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// No validation that user owns this document
const doc = db.query('SELECT * FROM documents WHERE id = ?', [req.params.docId]);
res.json(doc);
});Multi-tenant applications face severe JWT IDOR risks when tenant IDs are embedded in tokens but not validated against requested resources:
// Vulnerable multi-tenant implementation
app.get('/api/tenant/:tenantId/resources', (req, res) => {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Attacker modifies 'tenantId' claim to access other tenants
const tenantId = decoded.tenantId;
const resources = db.query('SELECT * FROM resources WHERE tenant_id = ?', [tenantId]);
res.json(resources);
});Even with proper JWT verification, IDOR persists if the application logic trusts claims without server-side ownership validation. The token verification only ensures the token is authentic, not that the claims represent authorized access.
JWT-Specific Detection
Detecting JWT IDOR requires both static analysis of token handling patterns and dynamic testing of authorization logic. middleBrick's JWT-specific scanning identifies these vulnerabilities through several techniques.
middleBrick's JWT scanner examines token parsing and claim usage patterns. It looks for common anti-patterns like trusting sub, role, or custom claims without server-side validation. The scanner tests endpoints by crafting JWTs with modified claims and observing responses:
// middleBrick test payload for JWT IDOR detection
{
"alg": "HS256",
"typ": "JWT",
"sub": "999999", // Modified subject claim
"role": "admin", // Escalated role claim
"tenantId": "666666" // Modified tenant claim
}The scanner verifies whether endpoints return data for modified claims, indicating IDOR vulnerabilities. It also tests for predictable JWT structure issues, such as using weak secrets or vulnerable algorithms like none.
middleBrick's dynamic analysis includes testing JWT endpoints with crafted tokens that have:
- Modified
subclaims to access other users' data - Escalated
roleorpermissionclaims - Invalid or expired tokens to test error handling
- Tokens with none algorithm to test algorithm confusion
The scanner also examines API specifications for JWT usage patterns. When analyzing OpenAPI specs, middleBrick cross-references token requirements with endpoint authorization logic to identify mismatches between documented security requirements and actual implementation.
middleBrick's LLM/AI security module specifically detects prompt injection vulnerabilities in AI-powered JWT processing systems, where attackers might manipulate token claims to influence AI model behavior or extract sensitive information from AI responses.
JWT-Specific Remediation
Fixing JWT IDOR requires a defense-in-depth approach that validates both token authenticity and resource ownership. The most critical principle: never trust client-side claims for authorization decisions.
Proper JWT IDOR remediation involves server-side validation of resource ownership:
// Secure implementation with ownership validation
app.get('/api/documents/:docId', (req, res) => {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// VALIDATE OWNERSHIP ON SERVER SIDE
const doc = db.query(
'SELECT * FROM documents WHERE id = ? AND user_id = ?',
[req.params.docId, decoded.sub]
);
if (!doc) {
return res.status(404).send('Document not found or unauthorized');
}
res.json(doc);
});For role-based access control with JWTs, implement server-side role validation using database lookups rather than trusting token claims:
// Secure role-based access control
app.get('/api/admin/dashboard', (req, res) => {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// VERIFY ROLE IN DATABASE, NOT FROM TOKEN CLAIM
const user = db.query('SELECT role FROM users WHERE id = ?', [decoded.sub]);
if (user.role !== 'admin') {
return res.status(403).send('Forbidden');
}
res.json(adminDashboardData);
});Implement proper JWT secret management and algorithm validation:
// Secure JWT verification
const jwt = require('jsonwebtoken');
function verifyToken(token) {
try {
// Verify with secret and algorithm
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256']
});
return decoded;
} catch (err) {
// Handle verification errors securely
if (err.name === 'JsonWebTokenError') {
throw new Error('Invalid token format');
}
if (err.name === 'TokenExpiredError') {
throw new Error('Token expired');
}
throw new Error('Token verification failed');
}
}Use middleware for consistent authorization checks across your API:
// Authorization middleware
function authorizeResource(requiredRole) {
return (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).send('Missing token');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Check user role in database
const user = db.query('SELECT role FROM users WHERE id = ?', [decoded.sub]);
if (!user || user.role !== requiredRole) {
return res.status(403).send('Insufficient permissions');
}
req.user = user;
next();
} catch (err) {
res.status(401).send('Invalid token');
}
};
}
// Usage
app.get('/api/admin/dashboard', authorizeResource('admin'), (req, res) => {
res.json(adminDashboardData);
});Implement token revocation and rotation strategies to limit the impact of compromised tokens. Use short expiration times (5-15 minutes for access tokens) and refresh tokens for extended sessions.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |