Skip to content

[ES-1944G] Added JWT expire time as TTL for jti cache#2018

Open
KashiwalHarsh wants to merge 12 commits into
mosip:developfrom
Infosys:ES-1944G
Open

[ES-1944G] Added JWT expire time as TTL for jti cache#2018
KashiwalHarsh wants to merge 12 commits into
mosip:developfrom
Infosys:ES-1944G

Conversation

@KashiwalHarsh

@KashiwalHarsh KashiwalHarsh commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened e-Sign client assertion and DPoP replay protection by applying TTL-aware JTI caching and fail-safe behavior when cache access encounters errors.
    • Improved client-assertion lifetime validation by enforcing a configurable maximum lifetime and rejecting tokens that are effectively expired beyond the allowed skew.
  • Chores
    • Updated JTI cache expiry settings to use a configurable max TTL (3600s) and skew buffer (30s) in default and test configurations.
  • Tests
    • Expanded unit tests to verify TTL calculation, lifetime cap enforcement, skew/expiry rejection, and replay/duplicate detection.

Signed-off-by: Harsh Kashiwal <harsh.kashiwal@infosys.com>
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

JTI replay protection now uses explicit TTL values throughout the cache, token, and DPoP paths. Configuration adds client-assertion JTI TTL controls, CacheUtilService accepts a TTL-aware replay check, TokenServiceImpl derives TTLs from token claims, and DPoP replay checks now pass a computed TTL.

Changes

TTL-aware JTI replay protection

Layer / File(s) Summary
Config properties and CacheUtilService TTL-aware JTI check
esignet-service/src/main/resources/application-default.properties, esignet-service/src/test/resources/application-test.properties, oidc-service-impl/src/main/java/io/mosip/esignet/services/CacheUtilService.java
New client-assertion JTI cache properties are added, the default JTI cache expiry is changed to reference the new max TTL property, and CacheUtilService now marks JTI replays with checkAndMarkJti(String, long) using Redis SET_IF_ABSENT with TTL or a Spring Cache fallback.
TokenServiceImpl client-assertion JTI replay protection
oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java
TokenServiceImpl adds JTI TTL cap and skew-buffer fields, routes client-assertion replay checks through enforceJtiReplayProtection, validates jti/exp/iat, derives a per-JTI TTL, and calls checkAndMarkJti(jti, ttlSeconds).
TokenServiceImpl JTI replay protection tests
oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java
Tests update the mock signature, configure JTI TTL-related fields, switch several JWT timestamps to current-time-relative values, and add lifetime validation cases that capture or suppress the TTL-aware cache call.
DPoP filter TTL computation and test stub update
esignet-service/src/main/java/io/mosip/esignet/advice/DpopValidationFilter.java, esignet-service/src/test/java/io/mosip/esignet/advice/DpopValidationFilterTest.java
DpopValidationFilter derives a DPoP JTI TTL from iat and age/skew limits before calling checkAndMarkJti(jti, ttlSeconds), and the DPoP tests are updated for the new signature and TTL capture.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • anushasunkada

Poem

🐇 I hopped through claims and counted time,
with TTL math in tidy rhyme.
Redis blinked, “No replay here!”
and DPoP kept its proof sincere.
A carrot-clock now guards the gate,
so tokens last just as theyేట్?

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: deriving JTI cache TTL from JWT expiry and using it for replay protection.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rachik-hue rachik-hue linked an issue Jun 22, 2026 that may be closed by this pull request
Comment thread esignet-service/src/main/resources/application-default.properties Outdated
Comment thread esignet-service/src/test/resources/application-test.properties Outdated
Signed-off-by: Harsh Kashiwal <kashiwalharsh1234@gmail.com>
Signed-off-by: Harsh Kashiwal <kashiwalharsh1234@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java (1)

240-243: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Cache TTL ignores the JWT validation clock-skew leeway; a replay window opens if skew-buffer-seconds < leeway-seconds.

The decoder (Line 201/292) accepts an assertion until now <= exp + maxClockSkew, but the cache entry created here only lives until exp + jtiCacheSkewBufferSeconds (TTL = (exp - now) + skewBuffer, set at now). With the defaults (skewBuffer=30, maxClockSkew=5) this is safe, but the two values are independently configurable, so raising mosip.esignet.client-assertion-jwt.leeway-seconds above skew-buffer-seconds leaves a window where the assertion still validates after the JTI entry has expired — i.e. a replayable token.

Couple the buffer to the validation leeway so coverage is guaranteed regardless of config.

🛡️ Proposed fix
         long jtiTtlSeconds = Math.min(
-                (exp - now) + jtiCacheSkewBufferSeconds,
+                (exp - now) + Math.max(jtiCacheSkewBufferSeconds, maxClockSkew),
                 maxJtiCacheTtlSeconds
         );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java`
around lines 240 - 243, The cache TTL calculation for JTI entries uses
jtiCacheSkewBufferSeconds, but the JWT decoder accepts tokens until now <= exp +
maxClockSkew (the validation leeway). When the validation leeway is larger than
the cache skew buffer, a replay vulnerability exists. Fix the TTL calculation in
the jtiTtlSeconds assignment to use the actual maxClockSkew value (the
validation leeway retrieved from the JWT decoder configuration) instead of
jtiCacheSkewBufferSeconds, or ensure the buffer is at least as large as the
leeway by using Math.max between the two values. This ensures cache entries
remain valid for at least as long as the JWT validation allows the token to be
accepted.
oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java (1)

420-446: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test does not exercise the branch it claims.

The comment states this validates rejection via the derived-TTL (jtiTtlSeconds <= 0) path, but with exp = now - 60s and maxClockSkew = 5, jwtDecoder.decode() rejects the assertion first (60s past >> 5s leeway). The assertion never reaches enforceJtiReplayProtection's expiry guard, so the test passes for the wrong reason. To genuinely target that branch you'd need a token that survives decode yet yields a non-positive TTL — which, per the production-file note, is currently unreachable. Consider asserting checkAndMarkJti is never invoked here, or adjusting clock-skew to reflect intent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java`
around lines 420 - 446, The test
verifyClientAssertion_expiredBeyondSkew_thenFail has timestamps that cause
jwtDecoder.decode() to reject the token before it reaches the
enforceJtiReplayProtection method, so the test exercises the wrong rejection
path despite the comment claiming otherwise. Either adjust the expiration and
issue times to be within the maxClockSkew threshold so the token passes decode
but fails the jtiTtlSeconds check in enforceJtiReplayProtection, or add a
verification (mock assertion or capture) to confirm checkAndMarkJti is never
invoked, demonstrating the actual early rejection by jwtDecoder.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java`:
- Around line 240-243: The cache TTL calculation for JTI entries uses
jtiCacheSkewBufferSeconds, but the JWT decoder accepts tokens until now <= exp +
maxClockSkew (the validation leeway). When the validation leeway is larger than
the cache skew buffer, a replay vulnerability exists. Fix the TTL calculation in
the jtiTtlSeconds assignment to use the actual maxClockSkew value (the
validation leeway retrieved from the JWT decoder configuration) instead of
jtiCacheSkewBufferSeconds, or ensure the buffer is at least as large as the
leeway by using Math.max between the two values. This ensures cache entries
remain valid for at least as long as the JWT validation allows the token to be
accepted.

In
`@oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java`:
- Around line 420-446: The test verifyClientAssertion_expiredBeyondSkew_thenFail
has timestamps that cause jwtDecoder.decode() to reject the token before it
reaches the enforceJtiReplayProtection method, so the test exercises the wrong
rejection path despite the comment claiming otherwise. Either adjust the
expiration and issue times to be within the maxClockSkew threshold so the token
passes decode but fails the jtiTtlSeconds check in enforceJtiReplayProtection,
or add a verification (mock assertion or capture) to confirm checkAndMarkJti is
never invoked, demonstrating the actual early rejection by jwtDecoder.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e01c3ecc-2d2f-4ec5-bfba-9e249ce6a8ad

📥 Commits

Reviewing files that changed from the base of the PR and between 332ff33 and a2d2ae5.

📒 Files selected for processing (3)
  • oidc-service-impl/src/main/java/io/mosip/esignet/services/CacheUtilService.java
  • oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java
  • oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java
💤 Files with no reviewable changes (1)
  • oidc-service-impl/src/main/java/io/mosip/esignet/services/CacheUtilService.java

Signed-off-by: Harsh Kashiwal <kashiwalharsh1234@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java (1)

420-447: 📐 Maintainability & Code Quality | 🟡 Minor

Update test comment to reflect that decode() performs expiration validation before JTI replay protection logic executes.

The test comment describes a jtiTtl = min(-60 + 30, 3600) = -30 calculation, but this code path is never reached. With maxClockSkew = 5 and the token expired 60 seconds, jwtDecoder.decode() (line 202) rejects the token as expired beyond the tolerance before enforceJtiReplayProtection is invoked (line 204). The INVALID_CLIENT exception and never().checkAndMarkJti(...) assertion pass due to the decode() rejection, not the JTI TTL logic. Rewrite the comment to clarify this execution flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java`
around lines 420 - 447, Update the comment in the
verifyClientAssertion_expiredBeyondSkew_thenFail test method to accurately
reflect that jwtDecoder.decode() performs expiration validation before JTI
replay protection logic. The current comment describes a jtiTtl calculation path
that is never reached because with maxClockSkew set to 5 seconds and the token
expired 60 seconds, the decode() method rejects the token as expired before
enforceJtiReplayProtection is invoked, which is why the INVALID_CLIENT exception
is thrown and checkAndMarkJti is never called. Rewrite the comment to clarify
this actual execution flow instead of describing unreachable code paths.
oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java (2)

232-238: 🩺 Stability & Availability | 🟠 Major

Assertion lifetime cap enforces security boundary; confirm intentionality with downstream clients.

The hard rejection of assertions with declaredLifetime > maxJtiCacheTtlSeconds is an intentional security design to ensure the JTI replay cache always outlives assertion validity, closing potential replay windows. However, this is a breaking change: any client issuing assertions valid for longer than the default 3600 seconds will now fail with INVALID_CLIENT.

RFC 7523 explicitly permits servers to reject assertions with "unreasonably far in future" expiration times, and security best practice recommends client assertion lifetimes of 1–5 minutes. The 3600-second default is already at the generous end of this spectrum.

If longer assertion lifetimes are required, the mosip.esignet.client-assertion.jti.cache.max-ttl-seconds property can be increased to match or exceed the declared assertion lifetime. Verify with consuming clients whether this validation is acceptable or if assertion lifetime expectations need adjustment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java`
around lines 232 - 238, The validation logic in TokenServiceImpl that rejects
client assertions with declaredLifetime exceeding maxJtiCacheTtlSeconds is an
intentional security control to prevent replay attacks, but it represents a
breaking change for clients using longer assertion lifetimes. Verify with
consuming clients whether their current assertion lifetime expectations align
with the default maxJtiCacheTtlSeconds constraint (3600 seconds), and
communicate that any assertions declaring lifetimes longer than this threshold
will be rejected with INVALID_CLIENT. If downstream clients require longer
assertion lifetimes, they can be directed to request an increase to the
mosip.esignet.client-assertion.jti.cache.max-ttl-seconds configuration property
to accommodate their needs, or they should adjust their client assertion
lifetime to comply with the security boundary enforced by this validation.

240-248: 🔒 Security & Privacy | 🟠 Major

Cache TTL can expire before assertion acceptance window closes, creating a replay vulnerability.

The decoder accepts assertions until exp + maxClockSkew (via JwtTimestampValidator at line 202), but the JTI cache TTL is capped at maxJtiCacheTtlSeconds (3600s by default). When an assertion with declaredLifetime near or at the cap is fresh (now ≈ iat), the TTL calculation:

jtiTtlSeconds = min((exp - now) + max(jtiCacheSkewBufferSeconds, maxClockSkew), maxJtiCacheTtlSeconds)

collapses to maxJtiCacheTtlSeconds, causing the cache entry to expire at now + 3600 while the token remains valid until exp + maxClockSkew = (now + declaredLifetime) + 5 seconds. This leaves a replay window of up to jtiCacheSkewBufferSeconds (or maxClockSkew if smaller) seconds between cache expiry and acceptance window closure, contradicting the line 232 invariant.

Adjust the TTL cap to include the skew: change maxJtiCacheTtlSeconds to maxJtiCacheTtlSeconds + Math.max(jtiCacheSkewBufferSeconds, maxClockSkew) in the Math.min().

Separately, the jtiTtlSeconds <= 0 guard (lines 245–248) is unreachable: the condition requires now ≥ exp + 30, but the upstream jwtDecoder.decode() at line 202 already rejects if now ≥ exp + maxClockSkew (5s by default), making this branch dead code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java`
around lines 240 - 248, The JTI cache TTL calculation in the Math.min() call for
jtiTtlSeconds has a replay vulnerability where the cache expires before the
token acceptance window closes. Adjust the first argument to Math.min() by
adding Math.max(jtiCacheSkewBufferSeconds, maxClockSkew) to the
maxJtiCacheTtlSeconds cap so the cache TTL accounts for the acceptance window
skew. Additionally, the jtiTtlSeconds <= 0 guard block is unreachable dead code
because upstream jwtDecoder.decode() validation already rejects expired tokens
before this point, so this entire if block checking for expired client
assertions can be removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java`:
- Around line 232-238: The validation logic in TokenServiceImpl that rejects
client assertions with declaredLifetime exceeding maxJtiCacheTtlSeconds is an
intentional security control to prevent replay attacks, but it represents a
breaking change for clients using longer assertion lifetimes. Verify with
consuming clients whether their current assertion lifetime expectations align
with the default maxJtiCacheTtlSeconds constraint (3600 seconds), and
communicate that any assertions declaring lifetimes longer than this threshold
will be rejected with INVALID_CLIENT. If downstream clients require longer
assertion lifetimes, they can be directed to request an increase to the
mosip.esignet.client-assertion.jti.cache.max-ttl-seconds configuration property
to accommodate their needs, or they should adjust their client assertion
lifetime to comply with the security boundary enforced by this validation.
- Around line 240-248: The JTI cache TTL calculation in the Math.min() call for
jtiTtlSeconds has a replay vulnerability where the cache expires before the
token acceptance window closes. Adjust the first argument to Math.min() by
adding Math.max(jtiCacheSkewBufferSeconds, maxClockSkew) to the
maxJtiCacheTtlSeconds cap so the cache TTL accounts for the acceptance window
skew. Additionally, the jtiTtlSeconds <= 0 guard block is unreachable dead code
because upstream jwtDecoder.decode() validation already rejects expired tokens
before this point, so this entire if block checking for expired client
assertions can be removed.

In
`@oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java`:
- Around line 420-447: Update the comment in the
verifyClientAssertion_expiredBeyondSkew_thenFail test method to accurately
reflect that jwtDecoder.decode() performs expiration validation before JTI
replay protection logic. The current comment describes a jtiTtl calculation path
that is never reached because with maxClockSkew set to 5 seconds and the token
expired 60 seconds, the decode() method rejects the token as expired before
enforceJtiReplayProtection is invoked, which is why the INVALID_CLIENT exception
is thrown and checkAndMarkJti is never called. Rewrite the comment to clarify
this actual execution flow instead of describing unreachable code paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e680e279-7c56-4bf3-8abd-91f4d61db55b

📥 Commits

Reviewing files that changed from the base of the PR and between a2d2ae5 and 802243c.

📒 Files selected for processing (2)
  • oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java
  • oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java

Signed-off-by: Harsh Kashiwal <kashiwalharsh1234@gmail.com>
Comment thread esignet-service/src/main/java/io/mosip/esignet/advice/DpopValidationFilter.java Outdated
KashiwalHarsh and others added 2 commits June 29, 2026 18:22
Signed-off-by: Harsh Kashiwal <harsh.kashiwal@infosys.com>
Signed-off-by: Harsh Kashiwal <kashiwalharsh1234@gmail.com>
Comment thread esignet-service/src/main/resources/application-default.properties
Comment thread oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java Outdated
Signed-off-by: Harsh Kashiwal <kashiwalharsh1234@gmail.com>
Comment thread oidc-service-impl/src/test/java/io/mosip/esignet/services/TokenServiceTest.java Outdated
Signed-off-by: Harsh Kashiwal <kashiwalharsh1234@gmail.com>
@KashiwalHarsh KashiwalHarsh requested a review from sacrana0 July 1, 2026 12:22
Comment thread oidc-service-impl/src/main/java/io/mosip/esignet/services/TokenServiceImpl.java Outdated
KashiwalHarsh and others added 3 commits July 3, 2026 16:14
Signed-off-by: Harsh Kashiwal <kashiwalharsh1234@gmail.com>
Signed-off-by: Harsh Kashiwal <harsh.kashiwal@infosys.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JTI cache has constant TTL

3 participants