Class: RackJwtVerifier::ReplayGuard
- Inherits:
-
Object
- Object
- RackJwtVerifier::ReplayGuard
- Defined in:
- lib/rack_jwt_verifier/replay_guard.rb
Overview
Records each verified token's jti in a cache store until the token
expires, and rejects a token whose jti is already there.
The store is the same read/write abstraction used for key material (InProcessCache, Rails.cache, ...). An in-process store only detects replays within one worker; use a shared store for real protection.
Failure mode is closed: a store that raises turns into ReplayCacheError (a 503 in the middleware). The operator asked for replay protection, so a request whose jti cannot be checked is not waved through.
Constant Summary collapse
- CACHE_KEY_PREFIX =
"rack_jwt_verifier:jti"- FALLBACK_TTL =
Used when a payload has no numeric
expto derive the TTL from (only possible when the operator removedexpfrom required_claims). 24 * 60 * 60
Instance Attribute Summary collapse
-
#leeway ⇒ Object
readonly
Returns the value of attribute leeway.
-
#store ⇒ Object
readonly
Returns the value of attribute store.
Instance Method Summary collapse
-
#cache_key(jti) ⇒ String
The cache key; jti is hashed so an attacker-chosen value cannot produce an unbounded or store-hostile key.
- #check!(payload) ⇒ Object
-
#initialize(store, leeway: 0) ⇒ ReplayGuard
constructor
A new instance of ReplayGuard.
Constructor Details
#initialize(store, leeway: 0) ⇒ ReplayGuard
Returns a new instance of ReplayGuard.
28 29 30 31 |
# File 'lib/rack_jwt_verifier/replay_guard.rb', line 28 def initialize(store, leeway: 0) @store = store @leeway = leeway end |
Instance Attribute Details
#leeway ⇒ Object (readonly)
Returns the value of attribute leeway.
24 25 26 |
# File 'lib/rack_jwt_verifier/replay_guard.rb', line 24 def leeway @leeway end |
#store ⇒ Object (readonly)
Returns the value of attribute store.
24 25 26 |
# File 'lib/rack_jwt_verifier/replay_guard.rb', line 24 def store @store end |
Instance Method Details
#cache_key(jti) ⇒ String
Returns the cache key; jti is hashed so an attacker-chosen value cannot produce an unbounded or store-hostile key.
56 57 58 |
# File 'lib/rack_jwt_verifier/replay_guard.rb', line 56 def cache_key(jti) "#{CACHE_KEY_PREFIX}:#{Digest::SHA256.hexdigest(jti)}" end |
#check!(payload) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/rack_jwt_verifier/replay_guard.rb', line 37 def check!(payload) jti = payload["jti"].to_s raise JWT::InvalidJtiError, "Missing jti" if jti.strip.empty? key = cache_key(jti) ttl = ttl_for(payload) # Read first so stores that ignore unless_exist still catch the common # case; the conditional write then closes the window for stores that # honour it (ActiveSupport stores return false when the key exists). seen, stored = store_access(key, ttl) raise ReplayedTokenError, "Token has already been used (jti #{jti})" if seen || stored == false nil end |