Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django jwt middleware for channels websocket authentication

I'm trying to set a Authentication middleware for django channels. I want this middleware to be active only for websocket requests.

Seems like that in this case i don't get a full middleware functionality. For example i can't get response = self.get_response(scope) working:

'TokenAuthMiddleware' object has no attribute 'get_response'

Everything is allright with this middleware now (it is activated only for websocket requests and not registered in settings.py), except that i need a means to modify a response status codes (block anonymous users and set the error code for ExpiredSignatureError). Any help appreciated. I use Django 2.0.6 and channels 2.1.1. jwt authentication by djangorestframework-jwt

middleware:

import jwt, re
import traceback
import logging

from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.conf import LazySettings
from jwt import InvalidSignatureError, ExpiredSignatureError, DecodeError

from project.models import MyUser

settings = LazySettings()
logger = logging.getLogger(__name__)


class TokenAuthMiddleware:
    """
    Token authorization middleware for Django Channels 2

    """

    def __init__(self, inner):
        self.inner = inner

    def __call__(self, scope):
        headers = dict(scope['headers'])
        auth_header = None
        if b'authorization' in headers:
            auth_header = headers[b'authorization'].decode()
        else:
            try:
                auth_header = _str_to_dict(headers[b'cookie'].decode())['X-Authorization']
            except:
                pass

        logger.info(auth_header)

        if auth_header:
            try:
                user_jwt = jwt.decode(
                    auth_header,
                    settings.SECRET_KEY,
                )
                scope['user'] = MyUser.objects.get(
                    id=user_jwt['user_id']
                )
            except (InvalidSignatureError, KeyError, ExpiredSignatureError, DecodeError):
                traceback.print_exc()
                pass
            except Exception as e:  # NoQA
                logger.error(scope)
                traceback.print_exc()

        return self.inner(scope)


TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))


def _str_to_dict(str):
    return {k: v.strip('"') for k, v in re.findall(r'(\S+)=(".*?"|\S+)', str)}

routing.py

application = ProtocolTypeRouter({
    # (http->django views is added by default)
    'websocket': TokenAuthMiddlewareStack(
        URLRouter(
            cmonitorserv.routing.websocket_urlpatterns
        )
    ),
})
like image 538
user1935987 Avatar asked Jun 18 '18 00:06

user1935987


1 Answers

Wasn't able to find a solution using middleware. For now solved by handling auth permissions in consumers.py

def _is_authenticated(self):
    if hasattr(self.scope, 'auth_error'):
        return False
    if not self.scope['user'] or self.scope['user'] is AnonymousUser:
        return False
    return True

Another important thing which doesn't seem to be documented anywhere - to reject a connection with the custom error code, we need to accept it first.

class WebConsumer(WebsocketConsumer):
    def connect(self):
        self.accept()
        if self._is_authenticated():
              ....
        else:
            logger.error("ws client auth error")
            self.close(code=4003)
like image 183
user1935987 Avatar answered Oct 21 '22 05:10

user1935987