Skip to content

feat: opt-in claim validation (--verify-claims, --aud, --iss) - #21

Merged
bsg62 merged 1 commit into
mainfrom
feat/verify-claims
Jul 25, 2026
Merged

feat: opt-in claim validation (--verify-claims, --aud, --iss)#21
bsg62 merged 1 commit into
mainfrom
feat/verify-claims

Conversation

@bsg62

@bsg62 bsg62 commented Jul 25, 2026

Copy link
Copy Markdown
Member

Implements #1 from the improvement list: optional, explicit claim validation that can affect the exit code — without changing the default decode-only behavior.

Behavior

  • --verify-claims validates the temporal claims (exp, nbf) and exits nonzero if the token is expired or not yet valid.
  • --aud <value> / --iss <value> additionally require a specific audience/issuer and imply validation (so they work without also passing --verify-claims).
  • The verdict prints as a Claims: VALID / Claims: INVALID section with the reason, and as claimsValid under --json.
$ jwtd --verify-claims <expired-token>
...
Claims: INVALID
  token is expired          # exit 1

Design decisions

  • Independent of signature verification. Claim validation performs no cryptography and runs with or without --key. When both are used, both sections are shown and the command exits nonzero if either fails (signature verdict takes precedence for the returned sentinel). This preserves the existing invariant that a bare decode never fails on expiry — nothing validates claims unless a claim flag is passed.
  • Shared clock. Uses go-jwt's jwt.NewValidator with jwt.WithTimeFunc(timeNow), so verdicts agree with the existing expired / not yet valid display annotations and are deterministic under the pinTime test helper. Reusing the library's validator avoids reimplementing crypto-adjacent checks.
  • Missing exp is not expired (standard JWT semantics); no leeway (matches the exact-time annotations).
  • JWT-only. Claim flags on a JWE emit a stderr note and are skipped (JWE claims live in the encrypted payload).
  • Low churn. decodeAndPrint is untouched; the human path runs claim validation as a separate section via a new decodeJWTHuman, and only decodeJWTJSON gained the claimChecks parameter.

Tests

New claims_test.go covers validateClaimsSet (live/expired/not-yet-valid, aud/iss match·mismatch·missing, joined multi-failures, missing-exp-is-valid), verifyClaims (VALID/INVALID output, sentinel, hard error on unparseable token), claimReason flattening, and integration through decodeJWTHuman / decodeJWTJSON (section ordering, both-checks precedence, claimsValid omitted when not requested). Full suite + go vet + gofmt clean.

Follow-up (not in this PR)

The marketing site's capabilities list still says signatures are checked "independently from claim validation"; adding a claim-validation capability entry there is a separate docs change I've left out to keep this PR focused.

🤖 Generated with Claude Code

Add explicit, opt-in RFC 7519 claim validation that can fail the exit
code, without changing the default behavior. --verify-claims enforces the
temporal claims (exp, nbf); --aud and --iss additionally require a specific
audience or issuer and imply validation. The result prints as a
Claims: VALID / INVALID section and is reported as claimsValid under --json.

Validation is deliberately independent of signature verification: it runs
with or without --key, shares the display clock (jwt.WithTimeFunc(timeNow))
so verdicts agree with the expired / not-yet-valid annotations, and uses
go-jwt's validator rather than reimplementing the checks. When both a key
and claim flags are given, both sections are shown and the command exits
nonzero if either fails, with the signature verdict taking precedence for
the returned sentinel. A missing exp is not treated as expired, and claim
flags on a JWE emit a note and are skipped.

The pre-existing invariant is preserved: a bare decode never fails on
expiry — nothing validates claims unless a claim flag is passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bsg62
bsg62 merged commit 34ca182 into main Jul 25, 2026
7 checks passed
@bsg62
bsg62 deleted the feat/verify-claims branch July 25, 2026 19:28
bsg62 added a commit that referenced this pull request Jul 25, 2026
Add a "Validate claims" capability card covering --verify-claims and the --aud/--iss assertions, placed after "Verify signatures" and renumbering the following cards. Reflects the opt-in claim validation shipped in #21.
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.

1 participant