Skip to content

fix(event-ledger): route JWT auth away from the API key evaluator - #1074

Draft
shelleyshen-0 wants to merge 1 commit into
mainfrom
fix/event-ledger-jwt-scope-check
Draft

fix(event-ledger): route JWT auth away from the API key evaluator#1074
shelleyshen-0 wants to merge 1 commit into
mainfrom
fix/event-ledger-jwt-scope-check

Conversation

@shelleyshen-0

Copy link
Copy Markdown
Contributor

TL;DR

JWT callers were sent to the API key policy evaluator, which accepts only an API key and rejects anything without one, so SIS and NVCA writes always failed. Route JWTs to local verification and scope checking instead, and accept the evaluator's actual verdict field so valid API keys are no longer read as denials.

Additional Details

Two independent defects made the service reject every request in self-managed deployments:

  • JWT and API key credentials both went to the evaluator. Its contract has a single apiKey input field, so a JWT produced a 400 that surfaced as 401.
  • The response type read result.allow, but the evaluator returns result.allowed, so approvals deserialized as denials.

Scope enforcement was also inert: the per-route scope wrappers were disabled under this provider, so token scopes were never compared against route requirements.

A dispatcher now selects the path by token shape. A JWT is verified against the configured JWKS and authorized by the route's scope check. An API key goes to the evaluator as before and skips the scope check, since it carries none. Managed deployments are unchanged: JWTs are still verified and then delegated to the policy decision point.

For the Reviewer

  • internal/middleware/dual_auth.go — new dispatcher
  • internal/middleware/policy.go — JWT handling removed, reads claims from context, accepts both verdict field names
  • internal/middleware/jwt.go — scope check passes through for evaluator-authorized requests
  • cmd/api/startup/run_service.go — wiring

For QA

Verified on a self-managed stack with an OpenBao-signed token for the sis-api role, both in-cluster and through the gateway:

Request Credential Result
POST /v3/ledger/cloudevents JWT, write scopes 200
GET events and stats same JWT 403
GET events and stats API key, read scopes 200
GET events none 401
GET /health none 200

Unit tests cover both paths, including scope enforcement driven through the real parser against a generated ES256 key and JWKS endpoint.

Note: requires the api-keys registration in #1062 for the API key path.

Issues

Relates to #82

Checklist

  • I am familiar with the Contributing Guidelines.
  • I have signed off my commits for Developer Certificate of Origin (DCO) compliance.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

The policy provider funnelled every credential to the API key policy
evaluator, whose contract requires an opaque API key. JWT-bearing callers
were therefore rejected, and because the per-route scope wrappers were
inert under that provider, token scopes were never enforced either.

Split the two credentials into independent paths chosen by token shape. A
JWT is verified against the configured JWKS and then authorized by the
per-route scope check. An API key is forwarded to the evaluator as before
and skips the scope check, since it carries no scopes. Requests the
evaluator authorizes are marked so the scope wrapper lets them through.

This removes the request-clone and no-op ResponseWriter workaround that
let the JWT parser run inside the policy middleware, and passes the
issuer and audience options through to the parser.

Also accept the evaluator's actual verdict field name. It reports
"allowed" while the response type only read "allow", so successful
evaluations deserialized as denials. The existing client test hardcoded
the wrong shape and masked this.

Adds coverage for both paths, including scope enforcement driven through
the real parser against a generated ES256 key and JWKS endpoint.
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d5c12083-0699-4f3b-a0e6-1e7363bff6f5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

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