Skip to content

feat: replace python-jose with PyJWT for token verification - #107

Open
HarryMWinters wants to merge 1 commit into
masterfrom
feat/switch-to-pyjwt
Open

feat: replace python-jose with PyJWT for token verification#107
HarryMWinters wants to merge 1 commit into
masterfrom
feat/switch-to-pyjwt

Conversation

@HarryMWinters

Copy link
Copy Markdown
Owner

Why

All 13 open Dependabot security alerts on this repo were in python-jose's transitive dependencies (cryptography, pyasn1, rsa, ecdsa). The python-ecdsa Minerva timing advisory (GHSA-wj6h-64fc-37mp) has no fix and upstream will not provide one, so it can only be cleared by dropping python-jose.

PyJWT with the crypto extra depends only on cryptography, and was already a dev dependency used by the test fixtures.

What changed

  • fastapi_oidc/auth.py now verifies with jwt.decode. The signing key is selected from the provider's JWKS by the token's kid header; a single-key JWKS is still accepted for tokens without a kid. Every PyJWTError maps to HTTPException(401) exactly as before.
  • Non-JWKS keys (a PEM string, a PyJWK) are passed through unchanged, so existing discovery stubs and monkeypatches keep working.
  • Minimum pyjwt[crypto] >= 2.13.0, which ships the fixes for algorithm allow-list bypass and key-family confusion when verifying with JWK keys (both relevant to this code path).
  • Removed python-jose[cryptography] and types-python-jose. ecdsa, rsa and pyasn1 are gone from the lockfile.
  • Added pyjwt[crypto] to the mypy pre-commit hook environment.
  • Docs (README, SECURITY.md, .agents, CHANGELOG) updated.

Public API

get_auth() signature and the returned authenticate_user dependency are unchanged. The verify_at_hash option is gone because PyJWT never checked at_hash; the old code disabled that check anyway.

Tests

New cases: JWKS key selection by kid, single-key JWKS without kid, unknown kid → 401, multi-key JWKS without kid → 401, token signed by a key not in the JWKS → 401, issuer passed as a generator. 36 tests pass; coverage 91% → 93%. Pre-commit hooks all pass.

🤖 Generated with Claude Code

python-jose drags in ecdsa, rsa and pyasn1, which carry open security
advisories (the ecdsa Minerva timing issue will never be fixed upstream).
PyJWT with the crypto extra needs only cryptography.

- auth.py: select the signing key from the JWKS by the token's kid
  header (single-key JWKS accepted for tokens without kid); decode with
  jwt.decode; map every PyJWTError to HTTPException(401).
- Non-JWKS keys (PEM strings, PyJWK) pass through unchanged so existing
  discovery stubs keep working.
- Require pyjwt[crypto] >= 2.13.0 for the JWK algorithm/key-confusion fixes.
- Drop python-jose and types-python-jose; add pyjwt to the mypy hook env.
- Tests: JWKS fixtures plus kid selection, unknown kid, multi-key-no-kid,
  wrong signing key, and generator-issuer cases (coverage 91% -> 93%).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-07T17:30:23.430586Z 8665001 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8665001162

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fastapi_oidc/auth.py
if not isinstance(keys, dict) or "keys" not in keys:
return keys

jwk_set = PyJWKSet.from_dict(keys)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor the token algorithm when constructing alg-less JWKs

When a provider publishes a valid RSA JWK without the optional alg member and signs ID tokens with PS256, RS384, or another advertised RSA algorithm, PyJWKSet.from_dict() binds that key to RS256 by default. PyJWT then rejects every otherwise-valid token because its header algorithm does not match the key's bound algorithm, even when the algorithm appears in the discovery allow-list. Construct the selected PyJWK using the token header's allowed algorithm (or pass an unbound public key) so alg-less JWKs continue to support the provider's advertised signing algorithms.

Useful? React with 👍 / 👎.

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