Verify tokens through rotation and revocation
Requires Node.js 22+ and npm. Uses pinned jose 6.2.12. All network servers bind
random ports on 127.0.0.1, close after each test, and issue synthetic identities.

Reference solution: not included here, so the exercise does not hand you the answer.
Attempt the repair first. Then download token-lifecycle-solution.zip from the project card
on https://softwaresecurity.com/labs/#project-token-lifecycle and extract it into this folder;
it adds reference.mjs without changing your files, and the reference commands below
need it.

Objective: distinguish a valid signature, the API's token contract, and current
authorization. Explain why removing a key from JWKS is not instant revocation.
Prerequisites: JWT header/payload/signature; basic JavaScript async functions.

1. [Needs internet access to the npm registry for npm ci; the rest runs locally.]
   Run npm ci --ignore-scripts, then npm test. Expect deliberate assertion
   failures, not an installation error. Start with the legitimate token test.
   It sends a signed token to a real HTTP API; jose fetches a real local JWKS.
2. In exercise.mjs pin issuer=https://issuer.example, audience=invoices-api,
   algorithms=[RS256], typ=at+jwt and required claims. The token_use claim must
   be access. Read the invalid[] table before changing code. Predict which cases
   signature verification alone cannot reject. Run npm test to check your answer.
3. Enforce the declared contract: nonblank subject/token ID, integer issued/expiry
   times, issuance no later than now, lifetime at most 300 seconds. jose checks
   expiry and nbf against the supplied deterministic clock with zero tolerance.
   These lifetime and purpose choices are this API's contract, not universal JWT
   rules. Never get a verification URL from an unverified token header or claim.
4. Consult state.sessions after cryptographic validation. Require an active
   session matching subject and tenant. Deny revoked jti values and disabled kid
   values. State unavailability must deny access. The test deliberately leaves
   the token cryptographically valid when these server-side facts change.
5. Trace rotation: A only -> A+B -> B only. First observe the cached-A test.
   Publishing only B does not erase A from a verifier's cache. An explicit trusted
   refresh removes A; a key deny entry handles the emergency immediately in this
   fixture. Record the HTTP fetch counts and explain the overlap window.
6. Run all 24 tests. Compare reference.mjs only after attempting the repairs.
   Reference run: EXERCISE_MODULE=reference npm test
   PowerShell: $env:EXERCISE_MODULE='reference'; npm test
   Remove the variable before evaluating your own implementation.

Keep: a table of test name, trusted decision input, expected status, and observed
status; a rollover timeline; an incident note explaining cached-key revocation.
Complete when: all 24 tests pass, same-token revocation takes effect, A/B overlap
works, and you can explain both why cached A still works and how it is stopped.
Stretch: make a separate test use a nonzero refresh cooldown and inject an unknown
kid flood. Design a refresh budget without preventing legitimate planned rollover.

Limits: actual signatures, JOSE claims and HTTP JWKS; in-memory session/revocation
state, no identity-provider login flow. Cache cooldown is zero for deterministic
tests, not a recommended production setting. Production requires HTTPS, issuer
configuration ownership, distributed revocation consistency, bounded refreshes,
clock monitoring, key-compromise response and authorization for each resource.
The trusted fixture controls the clock; callers never do. Generic 401 responses
avoid disclosing state; production should record bounded internal reason codes.

Sources (checked 9 October 2026):
https://github.com/panva/jose/blob/main/docs/jwks/remote/functions/createRemoteJWKSet.md
https://github.com/panva/jose/blob/main/docs/jwt/verify/functions/jwtVerify.md
https://www.rfc-editor.org/rfc/rfc8725
https://www.rfc-editor.org/rfc/rfc9068

Static analysis (optional)
semgrep/token-contract.yaml holds Semgrep rules for the defect in this project,
and semgrep/token-contract.mjs holds their test cases. With Semgrep installed
(python3 -m pip install semgrep), run from this folder:
   semgrep --test semgrep/
   semgrep scan --metrics=off --config semgrep/token-contract.yaml exercise.mjs
The starter is reported at line 10; the reference solution is not. Checked with
Semgrep 1.180.0. The rule finds a jwtVerify call without issuer and audience. It
cannot see the session, purpose and revocation checks the tests require after
verification.

Dev container
.devcontainer/devcontainer.json opens this folder in VS Code Dev Containers or
GitHub Codespaces using the Dev Containers Node.js 24 image, pinned by digest.
Creating the container runs npm ci --ignore-scripts. Run the commands above in
its terminal.
