Repository navigation
Conversation
jwt-cpp's default claim checks allow no leeway, so a resource server whose clock is even one second behind the issuer rejects a freshly issued token because its 'iat' is in the future. Issuers commonly backdate 'nbf' to absorb skew, but the strict 'iat' check defeated that. The failure was also reported as "token expired", which is misleading. Replace the 'iat' and 'nbf' checks with ones that allow a configurable skew (new int config key "validation.clock_skew_s", env var SCITOKEN_CONFIG_VALIDATION_CLOCK_SKEW_S, default 60s) and report which claim is in the future and by how much. 'exp' is still checked with no leeway. Refs #226 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pc7RS7J9BoUNRxvG6TZuzb
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The diagnostic rounds offsets inconsistently, and environment-loading coverage is missing.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds configurable clock-skew tolerance for future iat and nbf claims while retaining strict expiration checks.
Changes:
- Adds the
validation.clock_skew_sconfiguration and environment mapping. - Applies skew-aware validation with clearer errors.
- Adds unit coverage and public API documentation.
| File | Description |
|---|---|
src/scitokens.cpp |
Implements configuration storage and environment loading. |
src/scitokens.h |
Documents the new integer setting. |
src/scitokens_internal.h |
Applies skew-aware claim validation. |
test/main.cpp |
Tests skew boundaries, expiration, and invalid configuration. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Address review feedback: - Round the reported offset up to whole seconds so a rejection at, say, 60.5s is not reported as "60s in the future, more than ... 60s". Done manually since std::chrono::ceil is C++17. - Cover SCITOKEN_CONFIG_VALIDATION_CLOCK_SKEW_S in the startup env config test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pc7RS7J9BoUNRxvG6TZuzb
Replace the manual round-up with std::ceil on a fractional-seconds duration. std::chrono::ceil would be simpler but is C++17, and the build does not require C++17. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pc7RS7J9BoUNRxvG6TZuzb
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation matches the stated behavior and includes focused coverage for configuration, validation boundaries, errors, and strict expiration.
Review effort: Balanced
Findings: None
Resolved since last review (2)
djw8605
marked this pull request as ready for review
September 28, 2026 14:28
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Addresses #226.
Problem
Token time claims are checked by jwt-cpp's default verifier, which uses a leeway of 0. If the resource server's clock is even 1 second behind the issuer's, a freshly issued token fails the
iatcheck (now < iat). Issuers such as INDIGO IAM backdatenbfby 60s or more to absorb skew, but the strictiatcheck defeats that. On top of this, jwt-cpp reports the failure astoken_expired, which scitokens-cpp surfaces as aTokenExpiredExceptionwith "token expired". That is misleading for a token that looks like it was issued in the future.Change
validation.clock_skew_s(env varSCITOKEN_CONFIG_VALIDATION_CLOCK_SKEW_S), default 60 seconds. Negative values are rejected.Validator::verify_async_continue, the default jwt-cppiatandnbfchecks are replaced viawith_claimwith checks that allow up to that skew. They keep the jwt-cpp behavior of skipping a claim that is absent, so profiles wherenbfis optional are unaffected.token verification failed: 'iat' claim is 100s in the future, more than the allowed clock skew of 60s. It no longer mentions expiry, so it is not turned into aTokenExpiredException.expis still checked strictly (no leeway), so skew tolerance never extends a token's lifetime.scitokens.hnext to the other int config keys.Open questions for review
nbfbackdating IAM uses. It changes behavior for existing deployments (previously 0). Setvalidation.clock_skew_s=0to restore strict checking.validation.clock_skew_sis new, since the existing prefixes (keycache.,tls.,monitoring.) don't fit. Happy to rename.Validator::m_nowis set once when the object is constructed and is not refreshed on eachverify()unlessvalidator_set_time/enforcer_set_timeis called. Long-livedValidator/Enforcerobjects therefore check claims against a stale clock, which could cause the same symptom. That seems worth a separate change.Testing
SerializeTest.ClockSkew. It covers: accepted within the default skew; rejected beyond it, with an error that says "in the future" and not "expired"; accepted with a larger configured skew; rejected with skew 0;expnot extended by skew; negative value rejected.SerializeTest.*passes locally (18/18), and so do the monitoring and env config tests. In the full unit suite, 44 tests pass. The 3KeycacheTestrefresh tests fail only because the sandbox could not reachdemo.scitokens.org("Failure when receiving data from the peer"), unrelated to this change.🤖 Generated with Claude Code
https://claude.ai/code/session_01Pc7RS7J9BoUNRxvG6TZuzb
Generated by Claude Code