How to authenticate a user in websocket connection in django channels when using token authentication [duplicate]

只愿长相守 提交于 2019-12-03 09:04:08

The problem is that the browsers do not support passing jwt auth headers on websocket upgrade, so that's basically it. I faced this problem some time ago and came up with the solution of passing the token via query parameters - note that this is totally insecure without TLS as you expose the authentication in the URI. I don't have the access to the exact code anymore, but here is the idea:

from channels.generic.websockets import JsonWebsocketConsumer
from channels.handler import AsgiRequest

from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
from jwt.exceptions import InvalidTokenError
from rest_framework.exceptions import ValidationError

class Consumer(JsonWebsocketConsumer):

    def connect(self, message, **kwargs):
        # construct a fake http-like request object from the message
        message.content.setdefault('method', 'FAKE')
        request = AsgiRequest(message)
        # validate the token
        try:
            VerifyJSONWebTokenSerializer().validate(request.GET)
            super().connect(message, **kwargs)
        except (KeyError, InvalidTokenError, ValidationError,):
            # token is either not available or invalid
            # so we disconnect the user
            message.reply_channel.send({'close': True})

Register the consumer with

channel_routing = [
    ...
    route_class(Consumer, path=r'^my-ws-endpoint$'),
]

On browser side, you can establish the websocket connection by passing the token as query parameter in the websocket URI:

let token: string = 'my-token'; // get the token
let wsHandler: $WebSocket = new $WebSocket('wss://example.com/my-ws-endpoint/?token=' + token, ...);

You can then extract the auth check code in a decorator similar to @channel_session_user_from_http and just decorate your connection routines, or extract the code to a mixin if you use class-based routes.

I would like to repeat though that this approach is totally insecure without using encryption, so in production you URIs should start with https/wss.


Edit: here is a pretty nice solution for DRF token auth, suitable for both function-based and class-based routes. It has pretty much the same approach as mine, constructing a request object and passing it to the authenticator.

@hoefling's answer was my guide. I was confused about two things on authenticating a user.

  1. What to do with the token?

    • You can pass the token as a query string and get that query params. Read more about how to get the query params here.
    • Or if you are already passing it in the request's authorization header, you can get it from there like @hoefling did with his answer. Remeber to first fake that request.
  2. How validate that token and get the user?

    • Finally VerifyJSONWebTokenSerializer class was all I needed to validate the token, and get that token's user object. (Thanks @hoefling!) You can read the actual code of django-rest-framework-jwt here.

So, I ended up doing this way:

def ws_connect(message):
    message.content.setdefault('method', 'FAKE')
    django_request = AsgiRequest(message)
    token = django_request.GET['token'].split(' ')[1]
    try:
        data = {'token': token}
        valid_data = VerifyJSONWebTokenSerializer().validate(data)
        user = valid_data['user']
        ...
        ...
        message.reply_channel.send({
            "accept": True
        })
    except (KeyError, InvalidTokenError, ValidationError,):
        ...
        ...
        message.reply_channel.send({
            "text": "Authentication error",
            "close": True
        })
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!