Require login in a Django Channels socket? Require login in a Django Channels socket? django django

Require login in a Django Channels socket?


Django Channels already supports session authentication:

# In consumers.pyfrom channels import Channel, Groupfrom channels.sessions import channel_sessionfrom channels.auth import channel_session_user, channel_session_user_from_http# Connected to websocket.connect@channel_session_user_from_httpdef ws_add(message):    # Accept connection    message.reply_channel.send({"accept": True})    # Add them to the right group    Group("chat-%s" % message.user.username[0]).add(message.reply_channel)# Connected to websocket.receive@channel_session_userdef ws_message(message):    Group("chat-%s" % message.user.username[0]).send({        "text": message['text'],    })# Connected to websocket.disconnect@channel_session_userdef ws_disconnect(message):    Group("chat-%s" % message.user.username[0]).discard(message.reply_channel)

http://channels.readthedocs.io/en/stable/getting-started.html#authentication


Your function worked "as-is" for me. Before I walk through the details, there was a bug (now resolved) that was preventing sessions from being closed which may explain your other issue.

I use scarce quotes around "as-is" because I was using a class-based consumer so I had to add self to the whole stack of decorators to test it explicitly:

class MyRouter(WebsocketDemultiplexer):    # WebsocketDemultiplexer calls raw_connect for websocket.connect    @channel_session_user_from_http    @login_required_websocket    def raw_connect(self, message, **kwargs):        ...

After adding some debug messages to verify the sequence of execution:

>>> ws = create_connection("ws://localhost:8085")# server loggingchannel_session_user_from_http.runlogin_required_websocket.runuser: AnonymousUser# client loggingwebsocket._exceptions.WebSocketBadStatusException: Handshake status 403>>> ws = create_connection("ws://localhost:8085", cookie='sessionid=43jxki76cdjl97b8krco0ze2lsqp6pcg')# server loggingchannel_session_user_from_http.runlogin_required_websocket.runuser: admin

As you can see from my snippet, you need to call @channel_session_user_from_http first. For function-based consumers, you can simplify this by including it in your decorator:

def login_required_websocket(func):    @channel_session_user_from_http    @functools.wraps(func)    def inner(message, *args, **kwargs):        ...

On class-based consumers, this is handled internally (and in the right order) by setting http_user_and_session:

class MyRouter(WebsocketDemultiplexer):    http_user_and_session = True

Here's the full code for a self-respecting decorator that would be used with it:

def login_required_websocket(func):    """    If user is not logged in, close connection immediately.    """    @functools.wraps(func)    def inner(self, message, *args, **kwargs):        if not message.user.is_authenticated():            message.reply_channel.send({'close': True})        return func(self, message, *args, **kwargs)    return inner


My suggestion is that you can require a session key or even better take the username/password input within your consumer method. Then call the authenticate method to check if the user exists. On valid user object return, you can broadcast the message or return and invalid login details message.

from django.contrib.auth import authenticate@channel_session_userdef ws_message(message):    user = authenticate(username=message.username, password=message.password')     if user is not None:         Group("chat-%s" % message.user.username[0]).send({              "text": message['text'],              })     else:           # User is not authenticated so return an error message.