Secure Real-Time WebSockets With RabbitMQ

I wanted to add real-time notification support to my website.   Well not just my website but all my other native applications (Desktop/iPhone/Android).  I also needed to secure it with user authentication and authorization.

I started with a single Ruby on Rails application.  Rails has convenient built-in WebSocket support via Action Cable.  One problem with Action Cable is that it doesn’t scale.  There is a good article describing the pros and cons of Action Cable here:  https://www.ably.io/blog/rails-actioncable-the-good-and-the-bad

At LayerKeep we are using a micro-service architecture where we have different services written in different languages. Currently our inter-service communication is done via RabbitMQ.

I think RabbitMQ is awesome and when it comes to routing messages, it’s the best.  It’s also built on top of Erlang so it’s very fast. It has a plugin architecture that allows you to enable many different features.  And it’s very simple to use.  Since we are already using RabbitMQ for our inter-service communication I thought it would be nice to be able to use the same backend for handling our WebSocket connections.   

Rabbit has a nice plugin just for that using Web STOMP  (https://www.rabbitmq.com/web-stomp.html)

(* You can also use MQTT protocol instead of STOMP using https://www.rabbitmq.com/web-mqtt.html) .

Since this plugin ships in the core distribution, simply enable it with:

rabbitmq-plugins enable rabbitmq_web_stomp

Depending on how you configure stomp you could connect directly to it.  I personally like to proxy my requests through nginx.  If you are using nginx then something as simple as a location path like this should work.

location /ws {
  proxy_set_header Host $http_host;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  proxy_pass     http://your_rabbitmq_host:15674;
}

Then for you website you can find a nice javascript client to connect with stomp.  I personally use https://www.npmjs.com/package/webstomp-client.  Seems to work pretty well.  I’m also using React so I wrapped it in another component to handle all the connection and subscription events.   The nice thing about having the proxy is I don’t have to worry about changing the url or port.

import webstomp from 'webstomp-client';
setupWebSocket() {
   var protocol = document.location.protocol == "https:" ? 'wss' : 'ws';
   var wspath = `${protocol}://${document.location.hostname}/ws`
   var client = webstomp.client(wspath, {debug: false});   
   client.ws.onopen = this.onOpen
   this.setState( { client: client })
}

If you just want to receive transient notifications while connected, you can connect using a temporary queue.  As soon as the connection is disconnected the queue will be removed.  This means that if a notification is sent when a user refreshes the page the message wont be sent to the user.

You can also make the queue permanent so if messages come in while the user isn’t connected they will be persisted and as soon as the user connects they will receive all the previous messages.

What about Authentication/Authorization?

I really liked the idea of using Rabbit but needed to figure out how to handle authentication and authorization.  

RabbitMQ has a default internal user/password authentication that uses its own internal user database.  We don’t want to have to manage all ours users in multiple places just for queues so we need something dynamic.  Luckily there is a RabbitMQ plugin for handling auth using http (https://github.com/rabbitmq/rabbitmq-auth-backend-http).

When you try to connect to Rabbit with a username/password it will make a request to your backend server to authenticate that username/password.  This is great but I don’t want the user to have to enter their username and password again.  I figured authenticating using an OAuth token could work well.

RabbitMQ has auth_backends that it will try in order.  For example:

auth_backends.1.authn = internal
auth_backends.1.authz = rabbit_auth_backend_ip_range
auth_backends.2       = rabbit_auth_backend_http
auth_http.http_method   = post
auth_http.user_path     = http://auth_api/auth/user
auth_http.vhost_path    = http://auth_api/auth/vhost
auth_http.resource_path = http://auth_api/auth/resource
auth_http.topic_path    = http://auth_api/auth/topic
This configuration says that when a user tries to connect it will lookup the username and password in its internal database first.  If the user exists and is authenticated it will then check the IP address to see if the request is coming from an authorized IP address.  (That part isn’t needed but you can find it here: https://github.com/gotthardp/rabbitmq-auth-backend-ip-range)
If the user isn’t authed via auth_backends.1,  it will go to auth_backends.2.
By only specifying auth_backends.2, that means it will go to http backend for both authentication and authorization.
Now that we have the configured our backend, we need to go add those routes.
Depending on how complicated your authorization logic is, you might want a separate route for all them or you can add a single route.  In Rails it would be something like this:
post '/auth/:kind', to: 'rabbit#auth'
Then add your handler which should always return a 200 status with the permission (“allow” or “deny”)
def auth
  // Get the user

  user = User.find_by(username: params["username"])
  render status: :ok, json: "deny" and return unless user

  // First call will be to /auth/user

  if params["kind"] == "user"
    token = user.access_tokens.where(token: params["password"]).first
    if token.nil? || !token.accessible?
      permission = "deny"
    end
  else
    /***
     *** Handle all the other authorizations your app
     *** requires something like
     ***/

    if params["resource"] == "exchange" and params["permission"] != "read"
      permission = "deny"
    end
  end
  render status: :ok, json: permission and return
end

Look at the http backend plugin for all the other parameters.

You can find a docker image for RabbitMQ with http auth config here:  https://github.com/frenzylabs/rabbitmq.

AngularJS Navigation Menu

Once again I’m in need of creating a navigation menu with drop downs. This time I’ve been working with Angular and using Foundation 4 for styling.

The Final Output

Because who doesn’t like dessert first

Here is the fiddle. http://jsfiddle.net/kmussel/evXFZ/

The Styling

The good news is Foundation basically has all the styles already done for you under the class of “top-bar” and “top-bar-section”. But since I needed to have this nav under the top bar I just copied most of the top-bar styles and put it under the “nav-menu” class. You can see some of the styles I copied over at the bottom of the css panel in the fiddle.

You can also test out using only foundation’s top-bar styling by uncommenting out the html in the fiddle and commenting out the other “nav” element. And then change the directive name from “navMenu” to “anavMenu”. Make sure the result panel is wide enough though cause foundation changes the css on small screen sizes when it comes to the top-bar.
Basically if all you do is include foundation’s css the only thing you have to make sure you do is change your html to have a “top-bar” class and then a nested “top-bar-section” class.

The AngularJS Goodness

If you’ve never worked with angular I definitely recommend it. But it can be frustrating especially dealing with directives (which is what we will be doing here). I think this guy is spot on with how learning angular will make you feel.

So I basically knew what I wanted to be able to do and that is in html write this:

<nav menu-data="menu"></nav>

and it spit out my entire navigation.

And so I could resuse the directive with other controllers I added an attribute to it that would reference the scope variable where the menu data would be which led me to have this:

<nav menu-data="menu" menu-data="menu"></nav>

The first directive I create then is the navMenu one.


app.directive('navMenu', ['$parse', '$compile', function($parse, $compile)
{
    return {
        restrict: 'E', //Element
        scope:true,
        link: function (scope, element, attrs)
        {
            scope.$watch( attrs.menuData, function(val)
            {
                var template = angular.element('<ul id="parentTreeNavigation"><li ng-repeat="node in ' + attrs.menuData + '" ng-class="{active:node.active && node.active==true, \'has-dropdown\': !!node.children && node.children.length}"><a ng-href="{{node.href}}" ng-click="{{node.click}}" target="{{node.target}}" >{{node.text}}</a><sub-navigation-tree></sub-navigation-tree></li></ul>');
               var linkFunction = $compile(template);
               linkFunction(scope);
               element.html(null).append( template );
            }, true );
      }

}]);

In here i create the initial template. The template contains 2 other directives. Angular’s built-in “ng-repeat” and my other “sub-navigation-tree”. This is put inside a watch statement so if the menu changes it will be updated. The template is then compiled and linked to current scope and then appended to the current element.
This then compiles and links all the directives within that.

The Inner Directive handles the actual drop down part.



.directive('subNavigationTree', ['$compile', function($compile)
{
    return {
        restrict: 'E', //Element
        scope:true,
        link: function (scope, element, attrs)
        {
            scope.tree = scope.node;

            if(scope.tree.children && scope.tree.children.length )
            {
                var template = angular.element('<ul class="dropdown "><li ng-repeat="node in tree.children" node-id={{node.' + attrs.nodeId + '}}  ng-class="{active:node.active && node.active==true, \'has-dropdown\': !!node.children && node.children.length}"><a ng-href="{{node.href}}" ng-click="{{node.click}}" target="{{node.target}}" ng-bind-html-unsafe="node.text"></a><sub-navigation-tree tree="node"></sub-navigation-tree></li></ul>');

                var linkFunction = $compile(template);
                linkFunction(scope);
                element.replaceWith( template );
            }
            else
            {
                element.remove();
            }
        }
     };
}]);

Here you see the same thing with the template but it’s referencing itself now. The template is being linked to the current scope. This means that the directives within the template will be run using that scope. So ng-repeat=”node in tree.children” will be looping over scope.tree.children.
The ng-repeat then creates new scopes for each item and gives that scope access to the object through whatever you called it in your ng-repeat. In our case “node”. The scope that is created in the ng-repeat is the one that is passed to our directive.
Then if the current scope has children it does it all again with linking the template up with the current scope. It then replaces the current element with the template or removes it all together if there are no children.