Search code examples
angularcookieswebsocketamazon-cloudfront

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.


Solution

  • 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]);