VYPR
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.

PackageAffected versionsPatched versions
authlibPyPI
>= 1.6.5, < 1.6.71.6.7

Affected products

1

Patches

2
b87c32ed07b8

fix: remove "none" algorithm from default jwt instance

https://github.com/authlib/authlibHsiaoming YangFeb 6, 2026via ghsa
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",
    
a61c2acb8074

feat: support for the JWS 'none' alg

https://github.com/authlib/authlibÉloi RivardApr 20, 2025via ghsa
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

News mentions

0

No linked articles in our index yet.