[Fixed]-Require login in a Django Channels socket?

2👍

Django Channels already supports session authentication:

# In consumers.py
from channels import Channel, Group
from channels.sessions import channel_session
from channels.auth import channel_session_user, channel_session_user_from_http

# Connected to websocket.connect
@channel_session_user_from_http
def 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_user
def ws_message(message):
    Group("chat-%s" % message.user.username[0]).send({
        "text": message['text'],
    })

# Connected to websocket.disconnect
@channel_session_user
def ws_disconnect(message):
    Group("chat-%s" % message.user.username[0]).discard(message.reply_channel)

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

1👍

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 logging
channel_session_user_from_http.run
login_required_websocket.run
user: AnonymousUser

# client logging
websocket._exceptions.WebSocketBadStatusException: Handshake status 403

>>> ws = create_connection("ws://localhost:8085", cookie='sessionid=43jxki76cdjl97b8krco0ze2lsqp6pcg')

# server logging
channel_session_user_from_http.run
login_required_websocket.run
user: 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

0👍

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_user
def 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.
👤IVI

Leave a comment