lepture / authlib

The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS,JWE,JWK,JWA,JWT included.
https://authlib.org/
BSD 3-Clause "New" or "Revised" License
4.49k stars 448 forks source link

Authorization error responses are missing the state parameter #525

Open jaap3 opened 1 year ago

jaap3 commented 1 year ago

Describe the bug

While trying to implement a "silent signing" flow (using prompt=none) the library I use refuses to accept the error response (i.e. login_required) because the state parameter is missing from the response.

RFC 6749 section-4.1.2.1 has the following normative text about the required presence of the state parameter on authorisation error responses:

state REQUIRED if a "state" parameter was present in the client authorization request. The exact value received from the client.

For example, the authorization server redirects the user-agent by sending the following HTTP response:

HTTP/1.1 302 Found Location: https://client.example.com/cb?error=access_denied&state=xyz

To Reproduce

Send an authorization request with prompt=none&state=xyz while not authenticated.

Expected behavior

The error response contains the given state parameter.

Environment:

Additional context

OAuth2Error has a way to include the state parameter, but it's not always used. i.e. validate_request_prompt does not set it on the errors it raises. Other places do include the state parameter, i.e. validate_authorization_redirect_uri.

For the time being I've implemented a workaround on my side (Django), by setting the state parameter on OAuth2Errors raised by get_consent_grant:

class OAuth2AuthorizeView(AccessMixin, View):
    def get(self, request, *args, **kwargs):
        user = request.user if request.user.is_authenticated else None
        try:
            grant = oauth2_server.get_consent_grant(request, user)
            if grant.prompt == "login":
                # Redirect to login page
                return self.handle_no_permission()
        except OAuth2Error as error:
            logger.exception("Error during authorization request")
            # Make sure the state parameter is reflected in the error response
            error.state = request.GET.get("state")
            return oauth2_server.handle_error_response(
                oauth2_server.create_oauth2_request(request),
                error,
            )
        ....