Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Require login in a Django Channels socket?

I'm trying out Channels in Django 1.10 and set up a few consumers.

I tried creating a login_required decorator for it that closes the connection before executing it to prevent guests from entering this private socket. Also integrated unit tests afterwards to test it and they keep failing because it keeps letting guests in (AnonymousUser errors everywhere).

Also, sometimes when logging in and logging out the session doesn't clear and it lets the old user in.

The decorator:

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

Here's the consumer code:

def ws_connect(message, slug):
    message.reply_channel.send({ 'accept': True })
    client = message.reply_channel
    client.send(signal.message("Welcome"))
    try:
        # import pdb; pdb.set_trace()
        Room.objects.get(name=slug)
    except Room.DoesNotExist:
        room = Room.objects.create(name=slug)
        room.users.add(message.user)
        room.turn = message.user.id
        room.save()
        story = Story(room=room)
        story.save()


    # We made sure it exists.
    room = Room.objects.get(name=slug)
    message.channel_session['room'] = room.name

    # Check if user is allowed here.
    if not room.user_allowed(message.user):
        # Close the connection. User is not allowed.
        client.send(Signal.error("User isn't allowed in this room."))
        client.send({'close': True})

The strange thing is, when commenting out all the logic between client.send(signal.message)) forwards, it works just fine and unit tests pass (meaning guests are blocked and auth code does not run [hence AnonymousUser errors]). Any ideas?

Here's the tests too:

class RoomsTests(ChannelTestCase):

    def test_reject_guest(self):
        """
        This tests whether the login_required_websocket decorator is rejecting guests.
        """
        client = HttpClient()
        user = User.objects.create_user(
            username='test', password='password')

        client.send_and_consume('websocket.connect',
                                path='/rooms/test_room', check_accept=False)
        self.assertEqual(client.receive(), {'close': True})

    def test_accept_logged_in(self):
        """
        This tests whether the connection is accepted when a user is logged in.
        """
        client = HttpClient()
        user = User.objects.create_user(
            username='test', password='password')
        client.login(username='test', password='password')

        client.send_and_consume('websocket.connect', path='/rooms/test_room')

Am I approaching this wrong, and if I am, how do I do this (require auth) properly?

EDIT: Integrated an actions system to try something out, looks like Django channels is simply not picking up any sessions from HTTP at all.

@enforce_ordering
@channel_session_user_from_http
def ws_connect(message, slug):
    message.reply_channel.send({'accept': True})
    message.reply_channel.send(Action.info(message.user.is_authenticated()).to_send())

Just returns false.

EDIT2: I see it works now, I tried changing localhost to 127.0.0.1 and turns out it works now. Is there a way to make it detect localhost as a valid domain so it ports over the sessions?

EDIT3: Turns out I found the localhost vs 127.0.0.1 cookie issue haha. To not waste the bounty, how would you personally implement auth login_required in messages/channels?

edit4: While I still don't know why the thing didn't work, here's how I eventually changed my app around the issue:

I created an actions system. When entering in, the socket does nothing until you send it an AUTHENTICATE action through JSON. I separated logged in actions in guest_actions and user_actions. Once authenticated, it sets the session and you are able to use user_actions.

like image 282
Sergio E. Diaz Avatar asked Mar 26 '17 20:03

Sergio E. Diaz


People also ask

What is authentication middleware in Django?

In Django, middleware is a lightweight plugin that processes during request and response execution. Middleware is used to perform a function in the application. The functions can be a security, session, csrf protection, authentication etc.

What is AuthMiddlewareStack?

It allows read-only access to a user object in the scope . AuthMiddleware requires SessionMiddleware to function, which itself requires CookieMiddleware . For convenience, these are also provided as a combined callable called AuthMiddlewareStack that includes all three.

Is Redis needed for Django channels?

Redis Channel Layer channels_redis is the only official Django-maintained channel layer supported for production use. The layer uses Redis as its backing store, and it supports both a single-server and sharded configurations as well as group support. To use this layer you'll need to install the channels_redis package.


1 Answers

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

like image 198
Chris Montanaro Avatar answered Oct 21 '22 21:10

Chris Montanaro