From 3be08468201a7766a93012ce149ea12822cab096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Sun, 29 Mar 2026 09:21:42 +0200 Subject: [PATCH 1/2] fix: redirecting to unvalidated redirect_uri on UnsupportedResponseTypeError --- .../oauth2/rfc6749/authorization_server.py | 13 +++++- docs/changelog.rst | 31 +++++++++++++ .../test_authorization_code_grant.py | 44 +++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/authlib/oauth2/rfc6749/authorization_server.py b/authlib/oauth2/rfc6749/authorization_server.py index 928251dc..c484aa6c 100644 --- a/authlib/oauth2/rfc6749/authorization_server.py +++ b/authlib/oauth2/rfc6749/authorization_server.py @@ -241,10 +241,21 @@ def get_authorization_grant(self, request): if grant_cls.check_authorization_endpoint(request): return _create_grant(grant_cls, extensions, request, self) + # Per RFC 6749 ยง4.1.2.1, only redirect with the error if the client + # exists and the redirect_uri has been validated against it. + redirect_uri = None + if client_id := request.payload.client_id: + if client := self.query_client(client_id): + if requested_uri := request.payload.redirect_uri: + if client.check_redirect_uri(requested_uri): + redirect_uri = requested_uri + else: + redirect_uri = client.get_default_redirect_uri() + raise UnsupportedResponseTypeError( f"The response type '{request.payload.response_type}' is not supported by the server.", request.payload.response_type, - redirect_uri=request.payload.redirect_uri, + redirect_uri=redirect_uri, ) def get_consent_grant(self, request=None, end_user=None): diff --git a/docs/changelog.rst b/docs/changelog.rst index 1e557f58..c35a911b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,37 @@ Changelog Here you can see the full list of changes between each Authlib release. +Version 1.6.10 +-------------- + +**Unreleased** + +- Fix redirecting to unvalidated ``redirect_uri`` on ``UnsupportedResponseTypeError``. + +Version 1.6.9 +------------- + +**Released on Mar 2, 2026** + +- Not using header's ``jwk`` automatically. +- Add ``ES256K`` into default jwt algorithms. +- Remove deprecated algorithm from default registry. +- Generate random ``cek`` when ``cek`` length doesn't match. + +Version 1.6.8 +------------- + +**Released on Feb 17, 2026** + +- Add ``EdDSA`` to default ``jwt`` instance. + +Version 1.6.7 +------------- + +**Released on Feb 6, 2026** + +- Set supported algorithms for the default ``jwt`` instance. + Version 1.6.6 ------------- diff --git a/tests/flask/test_oauth2/test_authorization_code_grant.py b/tests/flask/test_oauth2/test_authorization_code_grant.py index f8d77fc9..6d437e2c 100644 --- a/tests/flask/test_oauth2/test_authorization_code_grant.py +++ b/tests/flask/test_oauth2/test_authorization_code_grant.py @@ -352,3 +352,47 @@ def test_token_generator(app, test_client, client, server): resp = json.loads(rv.data) assert "access_token" in resp assert "c-authorization_code.1." in resp["access_token"] + + +def test_missing_scope_empty_default(test_client, client, monkeypatch): + """When client.get_allowed_scope() returns empty string for missing scope, + the authorization should proceed without a scope. + """ + + def get_allowed_scope_empty(scope): + if scope is None: + return "" + return scope + + monkeypatch.setattr(client, "get_allowed_scope", get_allowed_scope_empty) + + rv = test_client.post(authorize_url, data={"user_id": "1"}) + assert "code=" in rv.location + + params = dict(url_decode(urlparse.urlparse(rv.location).query)) + code = params["code"] + headers = create_basic_header("client-id", "client-secret") + rv = test_client.post( + "/oauth/token", + data={ + "grant_type": "authorization_code", + "code": code, + }, + headers=headers, + ) + resp = json.loads(rv.data) + assert "access_token" in resp + assert resp.get("scope", "") == "" + + +def test_unsupported_response_type_does_not_redirect(test_client): + """Regression test for open redirect via unsupported response_type.""" + url = ( + "/oauth/authorize" + "?response_type=totally-unsupported" + "&redirect_uri=https%3A%2F%2Fevil.example%2Flanding" + "&state=s1" + ) + rv = test_client.get(url) + assert rv.status_code == 400 + assert rv.headers.get("Location") is None From ef09aebbba4439dedb22bd15777d1b3458b6f0ab Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Mon, 13 Apr 2026 22:29:14 +0900 Subject: [PATCH 2/2] chore: release 1.6.10 --- authlib/consts.py | 2 +- docs/changelog.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/authlib/consts.py b/authlib/consts.py index ed67bccf..1fc26196 100644 --- a/authlib/consts.py +++ b/authlib/consts.py @@ -1,5 +1,5 @@ name = "Authlib" -version = "1.6.9" +version = "1.6.10" author = "Hsiaoming Yang " homepage = "https://authlib.org" default_user_agent = f"{name}/{version} (+{homepage})" diff --git a/docs/changelog.rst b/docs/changelog.rst index c35a911b..2cdab361 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,7 +9,7 @@ Here you can see the full list of changes between each Authlib release. Version 1.6.10 -------------- -**Unreleased** +**Released on Apr 13, 2026** - Fix redirecting to unvalidated ``redirect_uri`` on ``UnsupportedResponseTypeError``.