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
)
),
})
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With