Time Of Check Time Of Use in Chi with Jwt Tokens
Time Of Check Time Of Use in Chi 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 that occurs when the outcome of a security decision depends on the timing of state changes between a check and the subsequent use of a resource. In Chi, this pattern commonly arises when JWT-based authorization is performed in two discrete steps: first validating or inspecting a token, and later using claims from that token to make authorization or routing decisions or to look up resources. If the underlying resource or the token validity can change between the check and the use, an attacker can exploit the window to escalate privileges, access other users’ data, or bypass intended constraints.
Consider a Chi endpoint that decodes a JWT to extract a subject identifier, then checks whether the subject is present in a database or a permissions store before performing an action. An attacker could revoke or swap the subject’s permissions, delete or reassign the associated record, or cause the lookup to return a different, higher-privileged subject between the check and the use. Because JWTs themselves can carry claims like roles or scopes, an attacker might also attempt to manipulate token contents if validation is weak (e.g., not verifying signatures or algorithms strictly). Even when tokens are valid, the resource they reference may be mutable, enabling IDOR-like scenarios when the authorization check does not revalidate at the point of use.
With JWT Tokens in Chi, this often manifests in handlers that decode the token once to decide access, then later use the same decoded object without re-verifying rights on the target resource. For example, a route that reads a user ID from a token and then fetches or updates that user’s data might inadvertently allow a modified request path or a delayed state to let one user act on another’s ID. Because Chi is a functional, composable router, handlers can be split across multiple layers or middleware, increasing the chance of inconsistent checks if authorization is not enforced atomically at the point of use.
Another scenario involves endpoints that inspect token metadata such as roles or permissions to gate access to certain operations. If these roles are cached or assumed to remain static, but backend permissions are updated or if the token’s claims are broader than intended, the check may pass while the use performs an action the user should not be allowed to perform. Similarly, when endpoints rely on claims to construct resource identifiers (for instance, building a path or query key from a subject claim), failing to revalidate that the subject has the right to access that specific resource at request time can lead to sensitive data exposure or modification.
To mitigate these risks, ensure that authorization checks and the use of derived data happen as a single, consistent operation wherever possible. Prefer re-evaluating permissions or reloading resource state at the moment of use rather than relying solely on earlier token inspection. When working with JWT Tokens in Chi, enforce strict validation on every request, avoid trusting claims for critical authorization decisions unless they are rechecked against the current state, and design handlers so that authorization logic is close to the data access layer to reduce the window for race conditions.
Jwt Tokens-Specific Remediation in Chi — concrete code fixes
Remediation centers on making authorization decisions at the point of use and revalidating critical claims against the current state. Below are concrete Chi handler patterns in F# that demonstrate secure handling of JWT Tokens.
1. Decode and revalidate within the same handler
Instead of decoding the token in middleware and passing claims forward, decode and verify the token inside each sensitive handler. This ensures the latest state and permissions are considered.
open System.IdentityModel.Tokens.Jwt
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Primitives
open System.Threading.Tasks
open FSharp.Control.Tasks
let requireVerifiedToken (jwtValidator: string -> ClaimsPrincipal option) (next: HttpFunc -> HttpRequest -> HttpFuncResult) : HttpFunc -> HttpRequest -> HttpFuncResult =
fun ctx req ->
let token = req.Headers["Authorization"]
|> StringValues.map (fun v -> v.ToString().Replace("Bearer ", ""))
|> Option.ofObj
|> Option.defaultWith (fun () -> raise (System.UnauthorizedAccessException("Missing token")))
match jwtValidator token with
| Some principal -> next ctx req
| None -> setStatusCode 401 >> text "Invalid token" ctx req
let getUserProfile (getUserInfo: string -> Async<UserInfo option>) : HttpFunc -> HttpRequest -> HttpFuncResult =
requireVerifiedToken validateToken (fun ctx req ->
task {
let! userInfo = getUserInfo principalId // principalId derived inside validateToken
return! match userInfo with
| Some u -> json u ctx req
| None -> setStatusCode 404 >> text "Not found" ctx req
})
2. Re-fetch resource ownership on each request
When endpoints act on a resource identified by a claim (e.g., subject or sub), re-fetch and verify ownership or permissions at the handler level rather than relying on the claim alone.
type ResourceService =
abstract GetResourceAsync: ResourceId * SubjectId -> Task<Resource option>
let updateResource (service: ResourceService) : HttpFunc -> HttpRequest -> HttpFuncResult =
requireVerifiedToken validateToken (fun ctx req ->
task {
let! body = req.readJsonAsync<UpdatePayload>()
let! resource = service.GetResourceAsync(body.ResourceId, principalSubjectId)
return!
match resource with
| Some r when r.OwnerId = principalSubjectId -> json r ctx req
| _ -> setStatusCode 403 >> text "Forbidden" ctx req
})
3. Avoid storing sensitive authorization decisions in claims
Do not encode mutable permissions or roles into JWT Tokens if they can change. Instead, keep roles lightweight and validate permissions against a live store at request time.
type PermissionService =
abstract HasPermissionAsync: SubjectId * Permission -> Task<bool>
let adminEndpoint (permissionService: PermissionService) : HttpFunc -> HttpRequest -> HttpFuncResult =
requireVerifiedToken validateToken (fun ctx req ->
task {
let! hasPerm = permissionService.HasPermissionAsync(principalSubjectId, "admin.perform")
if hasPerm then return! json "OK" ctx req
else return! setStatusCode 403 >> text "Insufficient permissions" ctx req
})
4. Use strict token validation settings
Ensure JWT Tokens are validated with strict settings on each request: validate issuer, audience, lifetime, and signature. Do not skip validation to "improve performance."
let validateToken (token: string) : ClaimsPrincipal option =
let handler = JwtSecurityTokenHandler()
let validationParams = new TokenValidationParameters(
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://auth.example.com",
ValidAudience = "https://api.example.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(System.Environment.GetEnvironmentVariable("JWT_SECRET")))
)
try
let principal = handler.ValidateToken(token, validationParams, &_)
Some principal
with
| _ -> None
5. Prefer short token lifetimes and refresh workflows
Short-lived JWT Tokens reduce the impact window for stale claims. Pair access tokens with refresh tokens and re-authorize sensitive operations on each request if necessary.
let accessTokenExpiry = TimeSpan.FromMinutes 15
// Issue tokens with minimal scopes; require re-validation for high-risk actions
6. Centralize authorization logic
Encapsulate authorization checks in dedicated functions that re-fetch state and validate permissions. This keeps handlers clean and ensures consistency.
let authorizeResourceOwner (resourceId: ResourceId) (subjectId: SubjectId) : Async<Result<unit, HttpCode> =
async {
let! resource = service.GetResourceAsync(resourceId)
return
match resource with
| Some r when r.OwnerId = subjectId -> Ok ()
| _ -> Error 403
}
7. Log and monitor suspicious patterns
Track mismatches between token claims and actual resource ownership to detect probing attempts that may indicate TOCTOU or IDOR testing.
// Example log entry for mismatched claims/resource ownership
// Level: Warn, Message: "Token subject 'userA' attempted access to resource owned by 'userB'"