Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong unauthorized WebSocket status code #1362

Closed
2 tasks done
andreadelfante opened this issue Dec 14, 2021 · 8 comments · Fixed by #1636
Closed
2 tasks done

Wrong unauthorized WebSocket status code #1362

andreadelfante opened this issue Dec 14, 2021 · 8 comments · Fixed by #1636
Labels
feature New feature or request websocket WebSocket-related

Comments

@andreadelfante
Copy link

Checklist

  • The bug is reproducible against the latest release and/or master.
  • There are no similar issues or pull requests to fix it yet.

Describe the bug

I have a protected websocket endpoint, with the decorator @requires('authenticated', status_code=401).
If the authentication check fails, the response code from the websocket is 403 Forbidden, but it should be 401 Unauthorized.

To reproduce

Here an example to reproduce the use case.

import uvicorn
from starlette.applications import Starlette
from starlette.authentication import AuthenticationBackend, AuthenticationError, AuthCredentials, SimpleUser, requires
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.websockets import WebSocket


class BasicAuthBackend(AuthenticationBackend):
    async def authenticate(self, request):
        auth = request.headers.get("Authorization", None)
        if auth != 'sample_key':
            raise AuthenticationError('Invalid auth credentials')

        return AuthCredentials(["authenticated"]), SimpleUser("username")


app = Starlette(
    middleware=[
        Middleware(AuthenticationMiddleware, backend=BasicAuthBackend()),
    ],
)


@app.websocket_route("/websocket")
@requires("authenticated", status_code=401)
async def websocket(websocket: WebSocket):
    await websocket.accept()
    await websocket.send_json({'message': 'Connected and authenticated!'})
    await websocket.close()


if __name__ == '__main__':
    uvicorn.run("websocket:app", host="0.0.0.0", port=10123)

Schermata 2021-12-14 alle 13 22 55

Expected behavior

The websocket endpoint should return 401, according to the status_code specified in the @requires decorator.

Actual behavior

The websocket endpoint should return 403, even if I specify the desired status_code in the @requires decorator.

Environment

  • OS: Linux
  • Python version: 3.8
  • Starlette version: 0.17.1
@aminalaee
Copy link
Member

I'm not 100% sure but after checking the code I think this might be happening In Uvicorn.

What do you think @Kludex ?

https://github.com/encode/uvicorn/blob/65ec8d132c37a8338a2f495fa1be9f14bd4368d9/uvicorn/protocols/websockets/websockets_impl.py#L236-L244

@Kludex
Copy link
Member

Kludex commented Dec 14, 2021

Is it related to this? encode/uvicorn#1181

@aminalaee
Copy link
Member

aminalaee commented Dec 15, 2021

I guess they are related, they point to the same code but not exactly the same issue.
I think this issue is here where we hard-coded http response to 403, and the @requires in Starlette has no effect on http response.

@aminalaee
Copy link
Member

I think this should probably be discussed in Uvicorn, but out of curiosity I've checked daphne websocket, and they're doing the same thing here:

def serverReject(self):
        """
        Called when we get a message saying to reject the connection.
        """
        self.handshake_deferred.errback(
            ConnectionDeny(code=403, reason="Access denied")
        )
        del self.handshake_deferred

The HTTP status of WebSocket rejection is hard-coded, I don't have much info about Uvicorn but maybe @Kludex or @euri10 can check the possibility to see if we can override that from Starlette.

@paulo-raca
Copy link

paulo-raca commented Feb 8, 2022

AuthenticationMiddleware sends websocket.close message, which maps to HTTP 403.

This is the only possible behavior with regular ASGI server, but there is an ASGI extension that allows sending custom HTTP responses.

I have implemented this for ExceptionMiddleware, should be straightforward to do the same for AuthenticationMiddleware

@paulo-raca
Copy link

Update: I implemented this in my PR: #1478

@Kludex
Copy link
Member

Kludex commented Apr 30, 2022

I see. Thanks for clarifying @paulo-raca. 👍

We'd still need to implement the extension on uvicorn's side.

@Kludex Kludex added feature New feature or request websocket WebSocket-related labels Apr 30, 2022
@Kludex Kludex added this to the Version 0.22.0 milestone Apr 30, 2022
@Kludex
Copy link
Member

Kludex commented May 12, 2022

We are going to document this behavior first on #1636.

At some point, I'll take a look at the implementation of the WebSocket Denial Response by @paulo-raca. There are a couple of things in the list before that, but we are going to get there - I'm not confirming we are going to have the extension, it needs to be discussed, and we need to see if it's worth it.

Note: An implementation on the uvicorn needs to take place as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request websocket WebSocket-related
Projects
None yet
4 participants