How do you authenticate token authentication website on django channels?

We want to use django feeds for our websites, but we also need to authenticate. We have a rest api working with the django-rest-framework, and there we use tokens for user authentication, but the same functionality does not seem to be built into the django channels.

+17
source share
4 answers

For Django-Channels 2 you can write custom authentication middleware https://gist.github.com/rluts/22e05ed8f53f97bdd02eafdf38f3d60a

token_auth.py:

from channels.auth import AuthMiddlewareStack from rest_framework.authtoken.models import Token from django.contrib.auth.models import AnonymousUser class TokenAuthMiddleware: """ Token authorization middleware for Django Channels 2 """ def __init__(self, inner): self.inner = inner def __call__(self, scope): headers = dict(scope['headers']) if b'authorization' in headers: try: token_name, token_key = headers[b'authorization'].decode().split() if token_name == 'Token': token = Token.objects.get(key=token_key) scope['user'] = token.user except Token.DoesNotExist: scope['user'] = AnonymousUser() return self.inner(scope) TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner)) 

routing.py:

 from django.urls import path from channels.http import AsgiHandler from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack from yourapp.consumers import SocketCostumer from yourapp.token_auth import TokenAuthMiddlewareStack application = ProtocolTypeRouter({ "websocket": TokenAuthMiddlewareStack( URLRouter([ path("socket/", SocketCostumer), ]), ), }) 
+18
source

This answer is valid for channels 1.

You can find all the information in this github release: https://github.com/django/channels/issues/510#issuecomment-288677354

I will summarize the discussion here.

  1. copy this mixin into your project: https://gist.github.com/leonardoo/9574251b3c7eefccd84fc38905110ce4

  2. apply decorator to ws_connect

The token is received in the application through an earlier authentication request in the /auth-token view in the django-rest-framework. We use the query string to send the token back to django channels. If you are not using django-rest-framework, you can use the query string in your own way. Read the mixin on how to get to it.

  1. After using mixin and using the correct token with an update / connection request, the message will have a user, as in the example below. As you can see, has_permission() implemented in the User model, so it can simply check its instance. If there is no token or the token is invalid, there will not be a single user in the message.
     # get_group, get_group_category and get_id are specific to the way we named
     # things in our implementation but I've included them for completeness.
     # We use the URL 'wss: //www.website.com/ws/app_1234? Token = 3a5s4er34srd32'

     def get_group (message):
         return message.content ['path']. strip ('/'). replace ('ws /', '', 1)


     def get_group_category (group):
         partition = group.rpartition ('_')

         if partition [0]:
             return partition [0]
         else:
             return group


     def get_id (group):
         return group.rpartition ('_') [2]


     def accept_connection (message, group):
         message.reply_channel.send ({'accept': True})
         Group (group) .add (message.reply_channel)


     # here in connect_app we access the user on message
     # that has been set by @rest_token_user

     def connect_app (message, group):
         if message.user.has_permission (pk = get_id (group)):
             accept_connection (message, group)


     @rest_token_user
     def ws_connect (message):
         group = get_group (message) # returns 'app_1234'
         category = get_group_category (group) # returns 'app'

         if category == 'app':
             connect_app (message, group)


     # sends the message contents to everyone in the same group

     def ws_message (message):
         Group (get_group (message)). Send ({'text': message.content ['text']})


     # removes this connection from its group.  In this setup a
     # connection wil only ever have one group.

     def ws_disconnect (message):
         Group (get_group (message)). Discard (message.reply_channel)


thanks to github leonardoo for sharing his mixin.

+10
source

I believe sending a token in the query string can reveal the token even inside the HTTPS protocols. To get around this, I used the following steps:

  1. Create a token-based REST API endpoint that creates a temporary session and responds with the session_key session (this session expires in 2 minutes)

     login(request,request.user)#Create session with this user request.session.set_expiry(2*60)#Make this session expire in 2Mins return Response({'session_key':request.session.session_key}) 
  2. Use this session_key in the query parameter in the channel parameter

I understand that there is one additional API call, but I believe that it is much safer than sending a token in the URL string.

Edit : This is just another approach to this problem, as discussed in the comments, get parameters are set only in the URLs of HTTP protocols, which should be avoided in any case.

+1
source

As for channels 1.x

As already mentioned here, mixin by leonardoo is the easiest way: https://gist.github.com/leonardoo/9574251b3c7eefccd84fc38905110ce4

I think, however, it is somewhat difficult to understand what mixin does and what does not, so I will try to make it clear:

When looking for a way to access message.user using the django decorative channel decorators, you need to implement it as follows:

 @channel_session_user_from_http def ws_connect(message): print(message.user) pass @channel_session_user def ws_receive(message): print(message.user) pass @channel_session_user def ws_disconnect(message): print(message.user) pass 

Channels do this by authenticating the user, creating an http_session and then converting http_session to channel_session, which uses the response channel instead of cookies to identify the client. All this is done in channel_session_user_from_http . Look at the source code of the channels for more details: https://github.com/django/channels/blob/1.x/channels/sessions.py

leonardoo decorator rest_token_user , however, does not create a channel session, it just saves the user in the message object in ws_connect. Since the token is not sent again to ws_receive, and the message object is also unavailable to get the user in ws_receive and ws_disconnect, you will need to save it in the session yourself. This would be an easy way to do this:

 @rest_token_user #Set message.user @channel_session #Create a channel session def ws_connect(message): message.channel_session['userId'] = message.user.id message.channel_session.save() pass @channel_session def ws_receive(message): message.user = User.objects.get(id = message.channel_session['userId']) pass @channel_session def ws_disconnect(message): message.user = User.objects.get(id = message.channel_session['userId']) pass 
0
source

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


All Articles