Skip to content

feat(auth): add JWT verification and OAuth client credentials - #8469

Open
bfreiberg wants to merge 1 commit into
aws-powertools:developfrom
bfreiberg:feat/auth-rfc-8466
Open

bfreiberg wants to merge 1 commit into
aws-powertools:developfrom
bfreiberg:feat/auth-rfc-8466

Conversation

@bfreiberg

Copy link
Copy Markdown

Add JWT verification, coordinated JWKS caching, API Gateway authorization, and OAuth client credentials with optional dependencies, documentation, examples, and tests.

Include exception-safe claims cleanup, sanitized provider errors, and lazy imports for OAuth-only clients and static-key verification.

Issue number: closes #8466

Summary

Changes

  • Add strict asymmetric JWT verification with mandatory issuer, resource audience, and expiration checks; additive claim requirements; static keys; OIDC discovery; and coordinated JWKS refresh with maximum age, unknown-key cooldown, and failure backoff.
  • Add resource-bound Cognito access-token verification, explicit multi-issuer composition, initialization prefetch, and a testing helper.
  • Integrate with Event Handler middleware and REST/HTTP API Gateway authorizers, including scope checks, exception-safe claims cleanup, sanitized errors, opt-in scalar context, and policies restricted to the current request.
  • Add OAuth client credentials using client_secret_basic, per-resource token caches, callable secrets, coordinated acquisition, bounded retries, and authenticated HTTPS requests.
  • Load the public classes independently so OAuth-only functions avoid JWT/cryptography imports, and static-key verifiers avoid loading HTTP transport. Public imports, introspection, and verification behavior are preserved.
  • Add 236 functional tests and 15 local HTTPS integration tests, API documentation, usage examples, and REST/HTTP SAM authorizer configuration with result caching disabled.
  • Declare PyJWT, cryptography, and urllib3 as optional dependencies, update the lock file, and add dependency-isolation coverage.

User experience

Applications configure the issuer, resource audience, and permitted algorithms, then use verify() directly, attach require() to a route, or return authorize() from a Lambda authorizer. Outbound clients use auth_headers() with their own HTTP client or call request() for a separate downstream resource.

verifier = JWTVerifier(
    issuer="https://idp.example.com/",
    audience="https://orders.example.com",
    algorithms=["RS256"],
    required_claims=["sub"],
)

@app.get("/orders", middlewares=[verifier.require(scopes=["orders:read"])])
def orders():
    return {"subject": app.context["claims"]["sub"]}

Validation

Check Result
Full non-performance regression suite, excluding AWS end-to-end tests 2,821 passed; 4 existing skips; 96.53% package coverage
Existing local performance suite 10 passed on the latest run; earlier timing variability is noted below
New Auth suite 251 passed on Python 3.10, 3.12, and 3.14, including 15 HTTPS integration tests and 5 fresh-process import checks
AWS deployment validation before import optimization 104 deployed checks passed on Python 3.12, x86_64 and arm64, in us-east-1
Lambda import-optimization comparison 600 cold and 600 warm invocations across 60 configurations; results below
Dependency isolation Auth-extra and base-only Nox sessions passed
Static analysis Ruff formatting/lint, mypy, ty, Bandit baseline, and Xenon complexity baseline passed
Documentation and deployment syntax MkDocs build, Markdownlint, and cfn-lint passed
Packaging and secrets Wheel/sdist build, optional-dependency metadata, TOML, diff checks, and Gitleaks passed
MCP example SDK 2.2.0 server initialization, signed-token mapping, and invalid-token denial verified

Functional tests use real asymmetric signatures and in-memory HTTP endpoints, without external services. They cover expiration and key removal, failed refresh, concurrent acquisition, async thread offloading during initial fetch/expiry/rotation, Cognito token purpose and resource binding, authorizer event fixtures, scope precedence, secret rotation, retry limits, and error redaction.

Regression tests exercise an unhandled protected handler followed by public, denied, and authenticated invocations, verifying that claims never survive the failed request. Error tests inspect __context__, __cause__, rendered tracebacks, and Powertools Logger output for public verification, prefetch, authorizer, and OAuth operations, including failing secret loaders invoked inside an existing exception handler.

Fresh-process tests block JWT/cryptography imports while constructing an OAuth client, and block urllib3 while verifying valid and invalid signatures against static JWKS. They also check that remote-cache construction loads the transport and that public exports retain introspection, unknown-attribute errors, and star-import behavior. Four of these checks failed before the optimization; all five pass afterward.

HTTPS integration tests run the production urllib3 transport against a loopback TLS server with a generated test certificate. They verify discovery/JWKS retrieval, signed-token verification, token exchange and downstream authentication, untrusted-certificate rejection before credentials are sent, response-size limits, stalled and slowly arriving bodies, separate downstream timeouts, and redirect/retry behavior.

Additional deployment checks used the SAM example with temporary AWS-hosted HTTPS fixtures. They exercised REST and HTTP Gateway decisions, middleware challenges, disabled authorizer caching, OAuth acquisition/concurrency/downstream requests, exception cleanup, and remote key rotation/removal/outages on both architectures. A real HTTP API $default route supplied a concrete method/path ARN, which the existing IAM helper successfully authorized. All three test stacks were deleted, with resource cleanup independently verified. These checks ran through a standalone harness; they do not represent the entire repository's AWS end-to-end suite or live Cognito/Keycloak interoperability testing.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

Add JWT verification, coordinated JWKS caching, API Gateway authorization,
and OAuth client credentials with optional dependencies, documentation,
examples, and tests.

Include exception-safe claims cleanup, sanitized provider errors, and lazy
imports for OAuth-only clients and static-key verification.
@bfreiberg
bfreiberg requested a review from a team as a code owner September 16, 2026 19:16
@bfreiberg
bfreiberg requested a review from svozza September 16, 2026 19:17
@boring-cyborg

boring-cyborg Bot commented Sep 16, 2026

Copy link
Copy Markdown

Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link

@boring-cyborg boring-cyborg Bot added dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests labels Sep 16, 2026
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Sep 16, 2026
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

if not isinstance(token, str) or not token:
raise InvalidTokenError()
try:
header = jwt.get_unverified_header(token)
try:
# This payload selects a configured verifier. No unverified claim
# is returned to callers or used to discover another provider.
payload = jwt.decode(token, options={"verify_signature": False})
@bfreiberg

Copy link
Copy Markdown
Author

I reviewed the two SonarCloud findings in verifier.py. They appear to flag intentional parsing before verification:

  • Line 257 — get_unverified_header(): reads the header to select a permitted algorithm and signing key. JWTVerifier.verify() then verifies the signature and validates the claims before returning them.
  • Line 308 — verify_signature=False: reads iss solely to select an explicitly configured verifier. Unknown issuers are rejected without network requests. The selected verifier performs full verification; the unverified payload is never
    returned to callers.

I reran the verifier and profile tests: 63 passed, including rejection of tampered signatures, tokens signed with another issuer’s key, and unknown issuers.

Could you review these as potential false positives in SonarCloud? The issuer-routing code already documents this behavior; I can add a similar explanation beside the header parsing.

@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.50000% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.46%. Comparing base (ceeb0c1) to head (56a6604).

Files with missing lines Patch % Lines
aws_lambda_powertools/utilities/auth/oauth2.py 90.00% 8 Missing and 8 partials ⚠️
aws_lambda_powertools/utilities/auth/verifier.py 87.20% 9 Missing and 7 partials ⚠️
...ws_lambda_powertools/utilities/auth/_authorizer.py 90.47% 3 Missing and 3 partials ⚠️
...lambda_powertools/utilities/auth/_authorization.py 92.72% 2 Missing and 2 partials ⚠️
aws_lambda_powertools/utilities/auth/_http.py 91.30% 2 Missing and 2 partials ⚠️
...ws_lambda_powertools/utilities/auth/_validation.py 87.87% 4 Missing ⚠️
aws_lambda_powertools/utilities/auth/__init__.py 76.92% 2 Missing and 1 partial ⚠️
aws_lambda_powertools/utilities/auth/_jwks.py 99.07% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #8469      +/-   ##
===========================================
- Coverage    96.66%   96.46%   -0.20%     
===========================================
  Files          296      310      +14     
  Lines        14911    15631     +720     
  Branches      1268     1372     +104     
===========================================
+ Hits         14413    15079     +666     
- Misses         363      394      +31     
- Partials       135      158      +23     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC: Auth utility for JWT verification and OAuth2 client credentials

2 participants