How to authenticate a user in a network connection in django channels when using token authentication

I am using the frontend framework ( Vuejs ) and the django-rest-framework for the REST API in my project. Also, for authenticating JSON web tokens, I use django-rest-framework-jwt . After a successful login, the user is given a token. This token is passed to each request to receive any API-related material.

Now I would like to integrate django channels in my project. Thus, after a successful login, when the token is received on the client side, I would like to initiate a connection to the website. Then on the server (consumer) I would like to check if the requested user is not anonymous. If the requested user is anonymous, I would like to close the connection or accept it.

Here is how I still do:

client side:

const socket = new WebSocket("ws://" + "dev.site.com"+ "/chat/"); 

routing.py:

 channel_routing = [ route("websocket.connect", ws_connect), ... ... ] 

consumers:

 def ws_connect(message): # if the user is no anonymous message.reply_channel.send({ "accept": True }) # else message.reply_channel.send({ "close": True }) 

The documentation has a @channel_session_user_from_http decorator that will provide message.user . But I use a token instead of a session. How can I check the user for a connection when using token authentication so that I can accept or close the connection. Or, if there is a better way, you could advise me that. Thanks.

+1
source share
2 answers

The problem is that browsers do not support the transfer of jwt auth headers when updating websocket, so basically that. Some time ago I ran into this problem and came up with a solution for passing a token through the request parameters - note that this is absolutely unsafe without TLS , since you are authenticating in the URI. I no longer have access to the exact code, 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 a user using

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

In the browser, you can establish a connection to the web server by passing the token as a request parameter to the website 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 authentication verification code in a decorator like @channel_session_user_from_http and simply decorate your connection procedures or extract the code in mixin if you use class-based routes.

I would like to repeat, however, that this approach is completely unsafe without the use of encryption, so when creating a URI, you should start with https/wss .


Edit : here is a pretty nice DRF authentication solution , suitable for both functions and class-based functions. It takes almost the same approach as mine, creating the request object and passing it to the authenticator.

+1
source
Answer

@hoefling was my guide. I was confused about two things when authenticating a user.

  • What to do with a token?

    • You can pass the token as a query string and get query parameters. Read more about how to get query parameters here .
    • Or, if you already pass it in the request authorization header, you can get it from there, as @hoefling did with its response. Remember to fake this query first.
  • How to check this token and get the user?

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

So, I ended up like this:

 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 }) 
0
source

Source: https://habr.com/ru/post/1266690/


All Articles