Time Of Check Time Of Use in Fiber with Jwt Tokens
Time Of Check Time Of Use in Fiber with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Time of Check Time of Use (TOCTOU) is a class of race condition where a system verifies a condition (a check) and later uses the result of that condition, but the underlying state may change between the check and the use. In a Fiber application that uses JWT tokens for authorization, this typically occurs when authorization logic depends on a token claim that can be observed or verified early in the request lifecycle and then used later after additional processing or branching. Because JWTs are self-contained and often validated once at the start of a request, developers may assume the claims they inspected remain trustworthy throughout the request. If the application performs an access control check based on a decoded claim and then performs an operation that depends on that claim without re-verifying context or permissions, an attacker may exploit timing or state changes to bypass intended restrictions.
Consider a scenario where a Fiber handler decodes a JWT to determine a user ID and role, checks whether the user is allowed to access a resource, and later performs an action such as updating a record. If the authorization check is done by inspecting the role claim and then the handler proceeds to make changes based on that role without ensuring the token is still valid for the specific action or re-evaluating context (for example, because the handler branches or calls sub-handlers asynchronously), an attacker may try to race modify server-side state or exploit timing differences between validation and usage. Additionally, if token validation (signature verification and claims checks) is done once and cached or reused across multiple authorization decisions within the same request, and one of those decisions involves sensitive operations, the lack of re-validation can lead to privilege escalation or unauthorized access consistent with BOLA/IDOR-style patterns.
In the context of the 12 security checks run by middleBrick, a scan may surface findings related to Authentication and BOLA/IDOR when JWT-based authorization is not consistently tied to the operation being performed or when claims are used beyond their intended scope without additional safeguards. For example, a handler might inspect the roles claim to authorize an endpoint but later invoke a service that performs administrative actions without confirming the token’s scopes or freshness in that narrower context. Because JWTs are often trusted after initial validation, the gap between the check (verifying claims) and the use (performing an action) can be exploited if the application does not enforce least privilege at the point of use or if it relies on unverified assumptions about the request path.
To detect such issues, middleBrick tests unauthenticated and authenticated attack surfaces, including how endpoints validate and consume JWT tokens. It checks whether authorization logic is tightly scoped to each operation and whether claims are appropriately re-evaluated for sensitive actions. Findings may include missing property-level authorization, weak input validation on claims, or patterns where excessive agency (such as use of function_call or tool_call patterns in downstream services) relies on unchecked token claims. These checks are especially relevant when LLM-related endpoints are exposed, as unauthenticated LLM endpoint detection can highlight routes where JWT usage is inconsistent with the sensitivity of the operation.
Jwt Tokens-Specific Remediation in Fiber — concrete code fixes
Remediation focuses on ensuring that authorization checks are performed close to the point of use and that JWT claims are validated in the context of each operation. Avoid relying on a single validation step for multiple distinct actions; instead, re-validate critical claims or re-derive permissions immediately before performing sensitive operations. Below are concrete, realistic code examples for Fiber that demonstrate secure handling of JWT tokens.
Example 1: Validate and use claims within the same handler
Keep token validation and the sensitive operation within the same function scope so there is no window where stale or broader claims are used.
const jwt = require('jsonwebtoken');
const { app } = require('fastify')(); // using fastify-like context for Fiber-style patterns
app.post('/resource/:id', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).send({ error: 'Unauthorized' });
}
const token = authHeader.substring(7);
let decoded;
try {
decoded = jwt.verify(token, process.env.JWT_SECRET);
} catch (err) {
return res.status(401).send({ error: 'Invalid token' });
}
// Use claims immediately for the specific operation
if (decoded.role !== 'admin') {
return res.status(403).send({ error: 'Insufficient permissions' });
}
const resourceId = req.params.id;
// Perform the sensitive action right after validation
performAdminAction(resourceId, decoded.sub);
res.send({ ok: true });
});
function performAdminAction(resourceId, userId) {
// Ensure userId matches the context of the operation
// Additional checks like resource ownership or scope checks can be applied here
}
Example 2: Re-validate claims for sensitive sub-operations
If your handler branches or delegates work, re-check the minimal claims needed for the sub-operation rather than reusing a broader earlier check.
const jwt = require('jsonwebtoken');
function updateUserSettings(token, settings) {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Re-validate only what is necessary for this specific update
if (!decoded.scopes || !decoded.scopes.includes('settings:write')) {
throw new Error('Insufficient scope for settings update');
}
// Proceed with update using decoded.sub as the subject
applySettings(decoded.sub, settings);
}
function applySettings(userId, settings) {
// Ensure userId is the subject of the token and matches the target
// Additional business logic and ownership checks should happen here
}
Example 3: Use middleware to enforce claims for specific routes
Define a middleware that verifies the token and enforces claim requirements, then apply it only to routes that need it, reducing the chance of using stale authorizations across unrelated handlers.
const jwt = require('jsonwebtoken');
function requireRole(role) {
return (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).send({ error: 'Unauthorized' });
}
const token = authHeader.substring(7);
let decoded;
try {
decoded = jwt.verify(token, process.env.JWT_SECRET);
} catch (err) {
return res.status(401).send({ error: 'Invalid token' });
}
if (decoded.role !== role) {
return res.status(403).send({ error: 'Forbidden: insufficient role' });
}
req.user = decoded;
next();
};
}
// Apply to specific routes
app.post('/admin/dashboard', requireRole('admin'), (req, res) => {
// req.user has already been verified with the required role
res.send({ message: 'Admin dashboard' });
});
General guidance
- Treat JWT claims as input and validate them close to the operation they affect.
- Avoid caching broad tokens or claims for use across multiple distinct actions within a request.
- Apply least privilege at the point of use: if a sensitive operation requires a specific scope or role, re-check it even if a broader check occurred earlier.
- Ensure token validation includes signature verification and, where applicable, checks on standard claims such as
exp,nbf, andiss.