HIGH race conditionchijwt tokens

Race Condition in Chi with Jwt Tokens

Race Condition in Chi with Jwt Tokens — how this specific combination creates or exposes the vulnerability

A race condition in the Chi web framework becomes significant when handling JWT token validation, particularly around token revocation and stateful checks. Chi is a functional, compositional router for Clojure applications, and it does not manage mutable session state by default. However, when developers introduce in-memory or external caches to track token validity (for example, to support logout or revocation), a timing gap can emerge between reading the revocation state and using the JWT for authorization.

Consider a scenario where a request reads a revocation flag from a cache (e.g., a Redis key indicating the token is revoked) and then proceeds to authorize business logic. If another request concurrently revokes that same token and updates the cache, the first request may still pass the check because it already read a stale “not revoked” value. This is a classic time-of-check-to-time-of-use (TOCTOU) pattern. Because JWTs are typically valid for a window and are accepted based on signature and claims, the framework-level code that decides whether to accept or reject a token must account for concurrent state changes.

In Chi, this can surface when middleware performs synchronous cache lookups before routing to handlers. For example, a developer might add a middleware that checks a blacklist keyed by JWT jti (JWT ID). If the lookup is non-atomic with the downstream authorization logic, an attacker could forge scenarios where a token is revoked after the check but before the handler uses it. This is particularly relevant for high-traffic APIs where revocation and issuance overlap. The risk is not in JWT cryptography (Chi does not alter token validation), but in how application-level checks interleave with Chi’s request processing pipeline.

Additionally, because Chi encourages small, pure functions composed into middleware, developers may inadvertently compose stateful checks that are not safe under concurrency. If the revocation store is eventually consistent or there is network latency between Chi’s handler chain and the cache, the window for a race increases. An attacker might issue many requests with the same JWT just after revocation, and some requests could slip through before the cache update propagates. The framework does not inherently protect against this; it is up to the developer to ensure that authorization checks and state updates are atomic or isolated per request.

Jwt Tokens-Specific Remediation in Chi — concrete code fixes

To mitigate race conditions involving JWT tokens in Chi, favor stateless validation where possible and avoid mutable revocation checks in the request path. When stateful checks are necessary, make them atomic and ensure they occur as late as possible in the handler chain. Below are concrete code examples for safer patterns.

Stateless JWT validation with middleware

Validate signature and claims without querying mutable state for every request. Use Chi’s middleware to decode and verify once, and attach the claims to the request map for downstream handlers.

(ns myapp.core
  (:require [cheshire.core :as json]
            [buddy.jwt :as jwt]
            [compojure.core :refer [defroutes GET]]
            [ring.middleware.anti-forgery :refer [anti-forgery-field]]
            [ring.util.response :as resp]))

(defn verify-jwt [secret]
  (fn [handler]
    (fn [request]
      (let [token (some-> request :headers (get "authorization") (re-find #"Bearer (\S+)") second)
            claims (when token (jwt/verify token secret))]
        (if claims
          (handler (assoc request :jwt-claims claims))
          (-> (resp/response {:error "Unauthorized"})
              (resp/status 401)))))))

(defroutes app-routes
  (GET "/public" [] "public data")
  (GET "/private" {claims :jwt-claims} (resp/response (str "Hello " (:sub claims))))
  (route/not-found "Not found"))

(def app
  (-> app-routes
      (verify-jwt "your-secret-key")))

Atomic revocation check using a single cache lookup

If you must check revocation, perform the check and the authorization decision in a single, atomic step. Avoid reading the revocation state early in the middleware and then using it later. Instead, pass the token to a handler that re-validates against the cache immediately before sensitive operations.

(ns myapp.revocation
  (:require [taoensso.carmine :as redis]
            [buddy.jwt :as jwt]))

(defn token-revoked? [token-id]
  (redis/with-conn {} (redis/exists (str "revoked:" token-id))))

(defn require-valid-jwt [handler]
  (fn [request]
    (let [token (some-> request :headers (get "authorization") (re-find #"Bearer (\S+)") second)
          claims (when token (jwt/verify token "your-secret-key"))
          jti (:jti claims)]
      (if (and claims (not (token-revoked? jti)))
        (handler (assoc request :jwt-claims claims))
        (-> (resp/response {:error "Token revoked or invalid"})
            (resp/status 401))))))

Leverage idempotent operations and short token lifetimes

Reduce the impact window by issuing short-lived JWTs and using refresh tokens stored server-side with strict invalidation semantics. For high-security endpoints, combine JWT validation with a lightweight database query that is guaranteed to see the latest state. In Chi, structure your routes so that sensitive handlers perform their own final check immediately before modifying state.

(ns myapp.secure-handler
  (:require [buddy.jwt :as jwt]
            [myapp.db :as db]))

(defn safe-transfer [request]
  (let [token (some-> request :headers (get "authorization") (re-find #"Bearer (\S+)") second)
        claims (jwt/verify token "your-secret-key")
        account-id (:sub claims)
        amount (:amount (:params request))]
    (if (db/can-transfer? account-id token) ; performs fresh revocation and balance check
      (db/execute-transfer account-id amount)
      {:status 403 :body {:error "Forbidden"}}))

Frequently Asked Questions

Can a race condition with JWT tokens in Chi be exploited even if tokens are cryptographically valid?
Yes. Even with valid signatures, a race condition in revocation checks (e.g., reading a revocation flag before it is written) can allow a revoked JWT to be accepted briefly. This is a stateful timing issue, not a cryptographic flaw.
Does Chi provide built-in protections against race conditions for JWT validation?
Chi does not provide built-in concurrency controls for application-level state like token revocation. Developers must design atomic checks and avoid non-atomic time-of-check-to-time-of-use patterns in their middleware and handlers.