Skip to content

Technical design — MOD-044 JWT role-based access control

Module: MOD-044 — JWT role-based access control System: SD07 Repo: bank-platform FR scope: FR-273, FR-274, FR-275, FR-276, FR-458, FR-459, FR-460, FR-461 Policies satisfied: DT-001 (GATE), GOV-007 (AUTO), GOV-006 (LOG) Author: Sub-agent (completed by orchestrator verification) Date: 2026-04-22 Dependencies: MOD-045 (Built), MOD-104 (Built) Deployed: yes — SST permalink https://sst.dev/u/e0493176

Objective

Issues, validates, revokes, and logs JWTs for every inter-module and API call in the bank platform. Signs RS256 with kid-indexed keys sourced from MOD-045's Secrets Manager namespace; exposes a Lambda authoriser that any API Gateway or service can invoke to enforce role → scope policy. Revocation list + refresh-rotation add defence against token theft.

Test evidence

AWS_PROFILE=bank-dev STAGE=dev pnpm test35 / 35 passing (26 unit + 9 integration).

FR / Policy Pass
FR-273 issue token with required claims
FR-274 validate signature / expiry / issuer / revoke
FR-275 kid-based key lookup + rotation grace window
FR-276 audit log immutable + 7y retention
FR-458 role → scope mapping
FR-459 scope gate before handler
FR-460 revocation list enforced
FR-461 refresh token rotation
DT-001 GATE signing key not readable by non-MOD-044 roles ✓ (negative)
GOV-007 AUTO every decision logged
GOV-006 LOG audit immutability

Stacks

MOD-044-jwt-rbac/src/stacks/: - signing-keys.ts — Active + previous signing key secrets in Secrets Manager (kid-indexed). MOD-045 rotation Lambda hooked for 90-day rotation. - authoriser-lambda.ts + issuer-lambda.ts + revoker-lambda.ts — the three Lambda handlers backing the JWT flows. - revocation-list.ts — DynamoDB table with TTL on expiry timestamp. - audit-log.ts — Dedicated CloudWatch log group with 7y retention + IAM-level deny on UPDATE/DELETE for every runtime role.

SSM outputs table

Path Value Consumer
/bank/{env}/mod044/authoriser/arn Lambda ARN API Gateway authoriser / cross-module Lambda invoke
/bank/{env}/mod044/issuer/arn Lambda ARN POST /auth/token + POST /auth/refresh invokers
/bank/{env}/mod044/revoker/arn Lambda ARN Ops + MOD-046 PAM session revocation
/bank/{env}/mod044/signing-key/current-kid String — current kid Token issuers
/bank/{env}/mod044/signing-key/previous-kid String — previous kid (grace window) Token validators
/bank/{env}/mod044/revocation-list/table-name DynamoDB table name Revoker + validator Lambdas
/bank/{env}/mod044/audit-log/group-arn Log group ARN Audit exporters, MOD-076 subscription filters

See MOD-044-jwt-rbac/src/outputs.ts for the complete list.

Key decisions

  • RS256 only. No HS256 — prevents key-confusion attacks.
  • Kid-based rotation grace window. Previous key stays valid for 24h after rotation so in-flight tokens remain accepted.
  • Revocation list TTL = token exp. DynamoDB removes entries when underlying tokens expire anyway, keeping the list bounded.
  • Refresh token rotation is server-side. Client presents old refresh → server issues new access + new refresh, invalidates old refresh. Reuse of old refresh → treat as compromise, revoke entire family.

Operational notes

  • Deploy: AWS_PROFILE=bank-dev pnpm sst deploy --stage <env> (from MOD-044-jwt-rbac/)
  • Tests: AWS_PROFILE=bank-dev STAGE=dev pnpm test
  • Audit log bucket is 7y retained — aws logs delete-log-group is blocked by the IAM immutability policy; if teardown is required, the orchestrator break-glass role must detach the policy first