pythonauthenticationautobahncrossbarwamp-protocol

Authentication to use for user notifications using Crossbar/Autobahn?


I'm currently trying to implement a user notification system using Websockets via Crossbar/Autobahn. I have done multiple tests and gone through the documentation, however, I'm not sure if there's a solution to having the following workflow work:

  1. User signs in with web app -- this is done through JWT
  2. Frontend establishes a websocket connection to a running crossbar instance.
  3. Frontend attempts to subscribe to a URI specifically for the user's notifications: i.e. com.example.notifications.user.23 or com.example.user.23.notifications'. Where23` is the user id.
  4. User's JWT is checked to see if user is allowed to access subscription.
  5. When activity is generated and causes a notification, the backend publishes the user-specific URIs.

For step 3, I can't tell if the current support auth methods have what I need. Ideally, I would like an auth method which I can customize (in order to implement a JWT authenticator within Crossbar) that I can apply to a URI pattern, but NOT give access to the entire pattern to the subscribing user. This is partially solved by the dynamic auth methods, but is missing the latter half:

For example (my ideal workflow):

  1. User attempts to subscribe to a URI com.example.user.23.notifications.
  2. URI matches com.example.user..notifications (wildcard pattern in http://crossbar.io/docs/Pattern-Based-Subscriptions/)
  3. Auth token is validated and user is given access to only com.example.user.23.notifications.

Is the above achievable in a simple way? From what I can tell, it may only be possible if I somehow generate a .crossbar/config.json which contains URI permutations of all user ids...and automatically generate a new config for each new user -- which is completely not a reasonable solution.

Any help is appreciated!


Solution

  • Use authorizer.

    See http://crossbar.io/docs/Authorization/#dynamic-authorization

    Register a dynamic authorizer for the user role that session was assigned when joining/authenticating:

               {
                  "name": "authorizer",
                  "permissions": [
                    {
                      "uri": "com.example.authorize",
                      "register": true
                    }
                  ]
                },
                {
                  "name": "authenticator",
                  "permissions": [
                    {
                      "uri": "com.example.authenticate",
                      "register": true
                    }
                  ]
                },
                {
                  "name": "user",
                  "authorizer": "com.example.authorize"
                },
    ...
    "components": [
        {
          "type": "class",
          "classname": "example.AuthenticatorSession",
          "realm": "realm1",
          "role": "authenticator",
          "extra": {
            "backend_base_url": "http://localhost:8080/ws"
          }
        },
        {
          "type": "class",
          "classname": "example.AuthorizerSession",
          "realm": "realm1",
          "role": "authorizer"
        }
      ]
    

    Write a class

    class AuthorizerSession(ApplicationSession):
        @inlineCallbacks
        def onJoin(self, details):
            print("In AuthorizerSession.onJoin({})".format(details))
            try:
                yield self.register(self.authorize, 'com.example.authorize')
                print("AuthorizerSession: authorizer registered")
            except Exception as e:
                print("AuthorizerSession: failed to register authorizer procedure ({})".format(e))
    
        def authorize(self, session, uri, action):
            print("AuthorizerSession.authorize({}, {}, {})".format(session, uri, action))
            if session['authrole'] == u'backend':  # backnend can do whatever
                return True
            [Authorization logic here]
            return authorized