4👍
The problem is that the browsers do not support passing jwt auth headers on websocket upgrade, so that’s basically it. I faced this problem some time ago and came up with the solution of passing the token via query parameters – note that this is totally insecure without TLS as you expose the authentication in the URI. I don’t have the access to the exact code anymore, but here is the idea:
from channels.generic.websockets import JsonWebsocketConsumer
from channels.handler import AsgiRequest
from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
from jwt.exceptions import InvalidTokenError
from rest_framework.exceptions import ValidationError
class Consumer(JsonWebsocketConsumer):
def connect(self, message, **kwargs):
# construct a fake http-like request object from the message
message.content.setdefault('method', 'FAKE')
request = AsgiRequest(message)
# validate the token
try:
VerifyJSONWebTokenSerializer().validate(request.GET)
super().connect(message, **kwargs)
except (KeyError, InvalidTokenError, ValidationError,):
# token is either not available or invalid
# so we disconnect the user
message.reply_channel.send({'close': True})
Register the consumer with
channel_routing = [
...
route_class(Consumer, path=r'^my-ws-endpoint$'),
]
On browser side, you can establish the websocket connection by passing the token as query parameter in the websocket URI:
let token: string = 'my-token'; // get the token
let wsHandler: $WebSocket = new $WebSocket('wss://example.com/my-ws-endpoint/?token=' + token, ...);
You can then extract the auth check code in a decorator similar to @channel_session_user_from_http
and just decorate your connection routines, or extract the code to a mixin if you use class-based routes.
I would like to repeat though that this approach is totally insecure without using encryption, so in production you URIs should start with https/wss
.
Edit: here is a pretty nice solution for DRF token auth, suitable for both function-based and class-based routes. It has pretty much the same approach as mine, constructing a request object and passing it to the authenticator.
0👍
@hoefling’s answer was my guide. I was confused about two things on authenticating a user.
-
What to do with the token?
- You can pass the token as a query string and get that query params. Read more about how to get the query params here.
- Or if you are already passing it in the request’s authorization header, you can get it from there like @hoefling did with his answer. Remeber to first fake that request.
-
How validate that token and get the user?
- Finally
VerifyJSONWebTokenSerializer
class was all I needed to validate the token, and get that token’s user object. (Thanks @hoefling!) You can read the actual code of django-rest-framework-jwt here.
- Finally
So, I ended up doing this way:
def ws_connect(message):
message.content.setdefault('method', 'FAKE')
django_request = AsgiRequest(message)
token = django_request.GET['token'].split(' ')[1]
try:
data = {'token': token}
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
...
...
message.reply_channel.send({
"accept": True
})
except (KeyError, InvalidTokenError, ValidationError,):
...
...
message.reply_channel.send({
"text": "Authentication error",
"close": True
})
- [Django]-Django ORM: Filtering by array contains OuterRef within a subquery yields zero results
- [Django]-Cannot urlencode() after storing QueryDict in session
- [Django]-Django1.4.0:ImportError: No module named base