Authlib JWS JWK Header Injection: Signature Verification Bypass
Published Mar 16, 2026 · Updated Nov 3, 2026
Original summary
Normalized public record · Updated Nov 3, 2026
Authlib JWS JWK Header Injection: Signature Verification Bypass
Publisher and source text is retained separately from QFinch interpretation.
QFinch assessment
A JWK Header Injection vulnerability in authlib's JWS implementation allows an unauthenticated attacker to forge arbitrary JWT tokens that pass signature verification. When key=None is passed to any JWS deserialization function, the library extracts and uses the cryptographic key embedded in the attacker-controlled JWT jwk header field. An attacker can sign a token with their own private key, embed the matching public key in the header, and have the server accept the forged token as cryptographically valid — bypassing authentication and authorization entirely. This behavior violates RFC 7515 §4.1.3 and the validation algorithm defined in RFC 7515 §5.2.
Potential impact
This is an authentication and authorization bypass vulnerability. Any application using authlib's JWS deserialization is affected when: - key=None is passed directly, or - a key resolver callable returns None for unknown/rotated kid values (the common JWKS lookup pattern) An unauthenticated attacker can impersonate any user or assume any privilege encoded in JWT claims (admin roles, scopes, user IDs) without possessing any legitimate credentials or server-side keys. The forged token is indistinguishable from a legitimate one — no exception is raised. This is a violation of RFC 7515 §4.1.3 and §5.2. The spec is unambiguous: the jwk header parameter is "NOT RECOMMENDED" as a key source, and the validation key MUST come from the application context, not the token itself. Minimal fix — remove the fallback from authlib/jose/rfc7515/jws.py:272-273: Recommended safe replacement — raise explicitly when no key is resolved:
Technical context
Vulnerable file: authlib/jose/rfc7515/jws.py Vulnerable method: JsonWebSignature.preparealgorithmkey() Lines: 272–273 When key=None is passed to jws.deserializecompact(), jws.deserializejson(), or jws.deserialize(), the library checks the JWT header for a jwk field. If present, it extracts that value — which is fully attacker-controlled — and uses it as the verification key. RFC 7515 violations: - §4.1.3 explicitly states the jwk header parameter is "NOT RECOMMENDED" because keys embedded by the token submitter cannot be trusted as a verification anchor. - §5.2 (Validation Algorithm) specifies the verification key MUST come from the application context, not from the token itself. There is no step in the RFC that permits falling back to the jwk header when no application key is provided. Why this is a library issue, not just a developer mistake: The most common real-world trigger is a key resolver callable used for JWKS-based key lookup. A developer writes: When an attacker submits a token with an unknown kid, the callable legitimately returns None. The library then silently falls through to key = header["jwk"], trusting the attacker's embedded key. The developer never wrote key=None — the library's fallback logic introduced it. The result looks like a verified token with no exception raised, making the substitution invisible. Attack steps: 1. Attacker generates an RSA or EC keypair. 2. Attacker crafts a JWT payload with any desired claims (e.g. {"role": "admin"}). 3. Attacker signs the JWT with their private key. 4. Attacker embeds their public key in the JWT jwk header field. 5. Attacker uses an unknown kid to cause the key resolver to return None. 6. The library uses header["jwk"] for verification — signature passes. 7. Forged claims are returned as authentic.
Evidence: public source recordsAffected products
| Affected package, product or range | Fix status |
|---|---|
| Consult the referenced vendor advisory for affected versions. | Consult source advisory |
Matching a version is only one part of assessing exposure. QFinch has not assessed your environment on this public page.
Remediation
Apply the vendor-supported fixed release or mitigation, then verify the affected component is no longer reachable.
- Confirm the affected product and reachable code path exist in your environment.
- Apply the vendor-supported patch or mitigation from the primary advisory.
- Rescan the exact deployed revision and retain verification evidence.
Evidence coverage
- SSVC priority
- MEDIUM
- Confidence
- Not available
- Aliases
- None reported
Scores appear only when corresponding evidence exists. Missing data is never interpreted as zero risk.
Sources and record history
- Security advisory ↗
- Patch commit ↗
- Source repository ↗
- access.redhat.com ↗
- access.redhat.com ↗
- access.redhat.com ↗
- access.redhat.com ↗
- access.redhat.com ↗
- access.redhat.com ↗
- bugzilla.redhat.com ↗
- security.access.redhat.com ↗
Record history
Published Mar 16, 2026 · Last refreshed Nov 3, 2026