Skip to content

Instantly share code, notes, and snippets.

@leonardoo
Created January 26, 2017 14:18
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
  • Save leonardoo/9574251b3c7eefccd84fc38905110ce4 to your computer and use it in GitHub Desktop.
Save leonardoo/9574251b3c7eefccd84fc38905110ce4 to your computer and use it in GitHub Desktop.
import functools
from channels.handler import AsgiRequest
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.settings import api_settings
authenticators = [auth() for auth in api_settings.DEFAULT_AUTHENTICATION_CLASSES]
def rest_auth(func):
"""
Wraps a HTTP or WebSocket connect consumer (or any consumer of messages
that provides a "cookies" or "get" attribute) to provide a "http_session"
attribute that behaves like request.session; that is, it's hung off of
a per-user session key that is saved in a cookie or passed as the
"session_key" GET parameter.
It won't automatically create and set a session cookie for users who
don't have one - that's what SessionMiddleware is for, this is a simpler
read-only version for more low-level code.
If a message does not have a session we can inflate, the "session" attribute
will be None, rather than an empty session you can write to.
Does not allow a new session to be set; that must be done via a view. This
is only an accessor for any existing session.
"""
@functools.wraps(func)
def inner(message, *args, **kwargs):
# Make sure there's NOT a http_session already
try:
# We want to parse the WebSocket (or similar HTTP-lite) message
# to get cookies and GET, but we need to add in a few things that
# might not have been there.
if "method" not in message.content:
message.content['method'] = "FAKE"
request = AsgiRequest(message)
except Exception as e:
raise ValueError("Cannot parse HTTP message - are you sure this is a HTTP consumer? %s" % e)
# Make sure there's a session key
user = None
auth = None
auth_token = request.GET.get("token", None)
if auth_token:
# comptatibility with rest framework
request._request = {}
request.META["HTTP_AUTHORIZATION"] = "token {}".format(auth_token)
for authenticator in authenticators:
try:
user_auth_tuple = authenticator.authenticate(request)
except AuthenticationFailed:
pass
if user_auth_tuple is not None:
message._authenticator = authenticator
user, auth = user_auth_tuple
break
message.user, message.auth = user, auth
# Make sure there's a session key
# Run the consumer
result = func(message, *args, **kwargs)
return result
return inner
def rest_token_user(func):
"""
saf Wraps a HTTP or WebSocket consumer (or any consumer of messages
that provides a "COOKIES" attribute) to provide both a "session"
attribute and a "user" attibute, like AuthMiddleware does.
This runs http_session() to get a session to hook auth off of.
If the user does not have a session cookie set, both "session"
and "user" will be None.
"""
@rest_auth
@functools.wraps(func)
def inner(message, *args, **kwargs):
# If we didn't get a session, then we don't get a user
if not hasattr(message, "auth"):
raise ValueError("Did not see a http session to get auth from")
return func(message, *args, **kwargs)
return inner
class RestTokenConsumerMixin(object):
rest_user = False
def get_handler(self, message, **kwargs):
handler = super(RestTokenConsumerMixin, self).get_handler(message, **kwargs)
if self.rest_user:
handler = rest_token_user(handler)
return handler
def connect(self, message, **kwargs):
if self.rest_user and not self.message.user:
self.close()
return message
@ergusto
Copy link

ergusto commented May 30, 2017

Is this meant to work with the current version of Channels? For the life of me I cannot get it working.

@emillundh
Copy link

emillundh commented Aug 23, 2017

Thanks! This is exactly what I needed. I forked and made three small edits:

  • line 55 must be changed from "pass" -> "user_auth_tuple = None" if you don't want uncaught exceptions thrown when failing to authenticate
  • To play with djangorestframework-jwt, 'token' should be 'JWT'. (Better still, add some logic to try both; I didn't bother here.)
  • token can be provided in request header instead of query params.

https://gist.github.com/emillundh/1448c8bb2f64a607202212f259f8edbc

@aodarc
Copy link

aodarc commented Aug 29, 2017

I found problem with disconnect after handshaking in your Mixin. Add super().connect(message, **kwargs) to connect method in the mixin.

@ahmadhaidarahmad
Copy link

@aodarc I'm stuck in the disconnect after handshaking it's not getting me the message.user where should i add the super().connect(message, **kwargs) can you help me in this please ?

@Bernix01
Copy link

@ahmadhaidarahmad you should put in the connect function of the mixin

    def connect(self, message, **kwargs):
        if self.rest_user and not self.message.user:
            self.close()
        super().connect(message, **kwargs)
        return message

@herobaby71
Copy link

herobaby71 commented Mar 6, 2018

Is there an update for Channel 2.0.2. Request is no longer there and I don't see how we can create a session without request.

@ThaJay
Copy link

ThaJay commented Mar 29, 2018

https://stackoverflow.com/a/49372334/5243543

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment