user1935987
user1935987

Reputation: 3347

WebSocket handshake authentication

I have an angular 6 app & python(django 2.0.6) back-end. Angular 6 app is deployed using AWS S3 & CloudFront.

Angular 6 app use websocket functionality. Everything works well locally, but when deployed Angular 6 app fails to pass a cookies with websocket handshake request. (Authorization is by cookies only for websoket connection. There are reasons for that, not relevant.)

Websocket server located at subdomain: `api.site.com``

i tried multiple ways to set cookies in angular 6 app, such as:

let domain = 'api.site.com'

document.cookie = `Token=${token}; domain=${domain}; path=/`;
document.cookie = `Token=${token}; path=/`;
document.cookie = `Token=${token};
document.cookie = `Token=${token}, domain=${domain}, path=/`;

using ngx-cookie-service:

this.cookieService.set('Token', token, undefined, '/', 'api.site.com');
this.cookieService.set('Token', token, undefined, '/');
this.cookieService.set('Token', token);

token is a JWT token.

For all the cases above, Token cookie does not passed with the websocket handshake when the app is deployed. using wss protocol. angular 6 app runs under the domain site.com (=> api is a subdomain )

Cloudfront behavior is set to pass all cookies.

Please advice what can be a possible reason.

Upvotes: 3

Views: 2769

Answers (1)

user1935987
user1935987

Reputation: 3347

Solution:

In my case i wasn't able to set the cookie, but made a solution based on based on this topic: HTTP headers in Websockets client API

There is a lot of scattered information on how to authenticate WebSocket client API with jwt token. Here is the complete solution which is valid for django 2.0.6, channels 2.1.1, angular 6 stack:

django middleware:

class TokenAuthMiddleware:

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

    def __call__(self, scope):
        auth_header = None
        if 'subprotocols' in scope:
            try:
                auth_header = scope['subprotocols'][1]
            except:
                pass

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

        return self.inner(scope)


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

ws consumer:

class WsConsumer(JsonWebsocketConsumer):
    def connect(self):
        self.accept('auth_token')
        if self._is_authenticated():
            ***do things***
        else:
            logger.error("ws client auth error")
            self.close(code=4003)

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

WebSocket client API (js cli):

  this.socket = new WebSocket('ws://host.com/ws/`, ['auth_token', token]);

Upvotes: 2

Related Questions