High severityNVD Advisory· Published Mar 6, 2026· Updated Mar 6, 2026
Authlib: Setting `alg: none` and a blank signature appears to bypass signature verification
CVE-2026-28802
Description
Authlib is a Python library which builds OAuth and OpenID Connect servers. From version 1.6.5 to before version 1.6.7, previous tests involving passing a malicious JWT containing alg: none and an empty signature was passing the signature verification step without any changes to the application code when a failure was expected.. This issue has been patched in version 1.6.7.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
authlibPyPI | >= 1.6.5, < 1.6.7 | 1.6.7 |
Affected products
1Patches
2b87c32ed07b8fix: remove "none" algorithm from default jwt instance
7 files changed · +36 −11
authlib/jose/__init__.py+16 −1 modified@@ -46,7 +46,22 @@ OKPKey.kty: OKPKey, } -jwt = JsonWebToken(list(JsonWebSignature.ALGORITHMS_REGISTRY.keys())) +jwt = JsonWebToken( + [ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "PS256", + "PS384", + "PS512", + ] +) __all__ = [
authlib/oauth2/rfc9101/authorization_server.py+3 −2 modified@@ -1,4 +1,5 @@ -from authlib.jose import jwt +from authlib.jose import JsonWebSignature +from authlib.jose import JsonWebToken from authlib.jose.errors import JoseError from ..rfc6749 import AuthorizationServer @@ -135,8 +136,8 @@ def _decode_request_object( self, request, client: ClientMixin, raw_request_object: str ): jwks = self.resolve_client_public_key(client) - try: + jwt = JsonWebToken(list(JsonWebSignature.ALGORITHMS_REGISTRY.keys())) request_object = jwt.decode(raw_request_object, jwks) request_object.validate()
authlib/oidc/core/grants/util.py+2 −2 modified@@ -3,7 +3,7 @@ from authlib.common.encoding import to_native from authlib.common.urls import add_params_to_uri from authlib.common.urls import quote_url -from authlib.jose import jwt +from authlib.jose import JsonWebToken from authlib.oauth2.rfc6749 import InvalidRequestError from authlib.oauth2.rfc6749 import scope_to_list @@ -111,7 +111,7 @@ def generate_id_token( payload["at_hash"] = to_native(at_hash) payload.update(user_info) - return to_native(jwt.encode(header, payload, key)) + return to_native(JsonWebToken([alg]).encode(header, payload, key)) def create_response_mode_response(redirect_uri, params, response_mode):
authlib/oidc/core/userinfo.py+4 −2 modified@@ -1,7 +1,7 @@ from typing import Optional from authlib.consts import default_json_headers -from authlib.jose import jwt +from authlib.jose import JsonWebToken from authlib.oauth2.rfc6749.authorization_server import AuthorizationServer from authlib.oauth2.rfc6749.authorization_server import OAuth2Request from authlib.oauth2.rfc6749.resource_protector import ResourceProtector @@ -74,7 +74,9 @@ def __call__(self, request: OAuth2Request): user_info["iss"] = self.get_issuer() user_info["aud"] = client.client_id - data = jwt.encode({"alg": alg}, user_info, self.resolve_private_key()) + data = JsonWebToken([alg]).encode( + {"alg": alg}, user_info, self.resolve_private_key() + ) return 200, data, [("Content-Type", "application/jwt")] return 200, user_info, default_json_headers
tests/flask/test_oauth2/test_jwt_authorization_request.py+5 −2 modified@@ -3,6 +3,7 @@ import pytest from authlib.common.urls import add_params_to_uri +from authlib.jose import JsonWebToken from authlib.jose import jwt from authlib.oauth2 import rfc7591 from authlib.oauth2 import rfc9101 @@ -213,7 +214,8 @@ def test_server_require_request_object_alg_none(test_client, server, metadata): metadata["require_signed_request_object"] = True register_request_object_extension(server, metadata=metadata) payload = {"response_type": "code", "client_id": "client-id"} - request_obj = jwt.encode( + jwt_none = JsonWebToken(["none"]) + request_obj = jwt_none.encode( {"alg": "none"}, payload, read_file_path("jwk_private.json") ) url = add_params_to_uri( @@ -277,7 +279,8 @@ def test_client_require_signed_request_object_alg_none(test_client, client, serv db.session.commit() payload = {"response_type": "code", "client_id": "client-id"} - request_obj = jwt.encode({"alg": "none"}, payload, "") + jwt_none = JsonWebToken(["none"]) + request_obj = jwt_none.encode({"alg": "none"}, payload, "") url = add_params_to_uri( authorize_url, {"client_id": "client-id", "request": request_obj} )
tests/flask/test_oauth2/test_openid_code_grant.py+3 −1 modified@@ -7,6 +7,7 @@ from authlib.common.urls import url_decode from authlib.common.urls import url_encode from authlib.common.urls import urlparse +from authlib.jose import JsonWebToken from authlib.jose import jwt from authlib.oauth2.rfc6749.grants import ( AuthorizationCodeGrant as _AuthorizationCodeGrant, @@ -340,7 +341,8 @@ def test_client_metadata_alg_none(test_client, server, app, db, client): headers=headers, ) resp = json.loads(rv.data) - claims = jwt.decode( + jwt_none = JsonWebToken(["none"]) + claims = jwt_none.decode( resp["id_token"], "secret", claims_cls=CodeIDToken,
tests/flask/test_oauth2/test_userinfo.py+3 −1 modified@@ -4,6 +4,7 @@ import authlib.oidc.core as oidc_core from authlib.integrations.flask_oauth2 import ResourceProtector from authlib.integrations.sqla_oauth2 import create_bearer_token_validator +from authlib.jose import JsonWebToken from authlib.jose import jwt from tests.util import read_file_path @@ -285,7 +286,8 @@ def test_scope_signed_unsecured(test_client, db, token, client): rv = test_client.get("/oauth/userinfo", headers=headers) assert rv.headers["Content-Type"] == "application/jwt" - claims = jwt.decode(rv.data, None) + jwt_none = JsonWebToken(["none"]) + claims = jwt_none.decode(rv.data, None) assert claims == { "sub": "1", "iss": "https://provider.test",
a61c2acb8074feat: support for the JWS 'none' alg
3 files changed · +8 −5
authlib/jose/rfc7518/jws_algs.py+1 −1 modified@@ -35,7 +35,7 @@ def sign(self, msg, key): return b"" def verify(self, msg, sig, key): - return False + return sig == b"" class HMACAlgorithm(JWSAlgorithm):
docs/changelog.rst+1 −1 modified@@ -14,7 +14,7 @@ Version 1.5.3 - Fix issue when :rfc:`RFC9207 <9207>` is enabled and the authorization endpoint response is not a redirection. :pr:`733` - Fix missing ``state`` parameter in authorization error responses. :issue:`525` - Support for ``acr`` and ``amr`` claims in ``id_token``. :issue:`734` - +- Support for the ``none`` JWS algorithm. Version 1.5.2 -------------
tests/jose/test_jws.py+6 −3 modified@@ -92,9 +92,12 @@ def test_compact_rsa_pss(self): self.assertRaises(errors.BadSignatureError, jws.deserialize, s, ssh_pub_key) def test_compact_none(self): - jws = JsonWebSignature() - s = jws.serialize({"alg": "none"}, "hello", "") - self.assertRaises(errors.BadSignatureError, jws.deserialize, s, "") + jws = JsonWebSignature(algorithms=["none"]) + s = jws.serialize({"alg": "none"}, "hello", None) + data = jws.deserialize(s, None) + header, payload = data["header"], data["payload"] + self.assertEqual(payload, b"hello") + self.assertEqual(header["alg"], "none") def test_flattened_json_jws(self): jws = JsonWebSignature()
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-7wc2-qxgw-g8ggghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-28802ghsaADVISORY
- github.com/authlib/authlib/commit/a61c2acb807496e67f32051b5f1b1d5ccf8f0a75ghsax_refsource_MISCWEB
- github.com/authlib/authlib/commit/b87c32ed07b8ae7f805873e1c9cafd1016761df7ghsax_refsource_MISCWEB
- github.com/authlib/authlib/security/advisories/GHSA-7wc2-qxgw-g8ggghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.