Skip to main content

WebSocket authentication in ColdFusion 10

I was looking into ways in which users can be authenticated before they start to receive or send messages over a WebSocket channel in ColdFusion. I found that there are two ways in which this can be done. The first approach is to call the 'authenticate' method on the socket object. The other way is to use cflogin to authenticate the user. The authentication level can be taken a step further in various channel listener functions. By implementing these listener functions, some sort business logic can be devised wherein the logged in user with certain credentials can be allowed to subscribe or publish to a channel.

As explained in my previous post, you can define multiple channels in Application.cfc by providing the name of the channel in this.wschannels variable.  By default, these channels are associated with the channel listener – ‘CFIDE.websocket.ChannelListener’. There are various methods defined in this ChannelListener, you can override the default functionality by extending this channel listener. The user defined channel listener can then implement some of the methods that give more control on various functionalities (explained below). Once this is in place, you can let the application know that you are using a custom channel listener by providing a value to the attribute cfclistener for the channel:

component { this.name = "WebSocketAPP"; this.wschannels = [{name="myChannel", cfclistener = "ChannelListenerComponent"}]; }
Once a connection is established with the WebSocket server, it would be a good idea to authenticate the user. The authenticate method on the socket object can be called by providing the username and password details:

function messageHandler(msg) { if(msg.type == "response") { if(msg.reqType == "welcome") { //authenticate once the connection is established successfully socket.authenticate("admin","admin"); } console.log('In message handler ' + msg.reqType); } if(msg.code == -1) { console.log(msg.msg); } }
The JavaScript function 'messageHandler' would be called whenever a message or an acknowledgement from the server is received. As you can see the authenticate method is called on the websocket object (socket) with username and password. When this method is called the onWSAuthenticate method defined in Application.cfc would be invoked. In this method the credentials can be validated and a struct variable connectionInfo can be set:

function onWSAuthenticate(String username, String password, Struct connectionInfo) { if(username == password) { connectionInfo.authenticated = "YES"; connectionInfo.role = "admin"; return true; } connectionInfo.authenticated = "NO"; return false; } }
As you can see this method takes an additional argument connectionInfo. On this object the key ‘authenticated’ can be set to 'Yes' to indicate that the user has been authenticated. Now the channel listener functions can check for the authentication of the user by referring to these keys. For example, the ChannelListenerComponent (mentioned above) can extend 'CFIDE.websocket.ChannelListener' and override the methods allowSubscribe, allowPublish and check whether the user is allowed to subscribe or publish:

component extends="CFIDE.websocket.ChannelListener" { public boolean function allowSubscribe(Struct subscriberInfo) { if(subscriberInfo.connectionInfo.authenticated == 'YES' && subscriberInfo.age > 18) return true; return false; } public boolean function allowPublish(Struct publisherInfo) { if(publisherInfo.connectionInfo.role == 'admin' && publisherInfo.name == 'Sagar') return true; return false; } }
The methods 'allowSubscribe' and 'allowPublish' have a single parameter of type struct, containing the subscriber\publisher information. These are invoked when the user calls the subscribe or the publish method on the socket object. While calling these methods additional information such as age or name can also be included:

socket.subscribe("myChannel",{age:25},myChannelHandler);

socket.publish("myChannel",”message_here”, {name:'Sagar'});

Since we have already authenticated the user and assigned a role; the connectionInfo object which is available for the methods mentioned above would be containing these values (assigned earlier in onWSAuthenticate). One can check for the same and allow\disallow the user to subscribe\publish by returning a boolean value.

The other way of authenticating users is to specify useCFAuth=true in the cfwebsocket tag. Here a user is authenticated using cflogin and cfloginuser tag and this will make the connectionInfo struct available in listener functions. Also, since the user is authenticated using cflogin you need not invoke the authenticate method on the socket object.

Comments

  1. To be clear, is this portion in the JS there to handle a bad auth?

    if(msg.code == -1) { 

    If so, is there a listing of what codes mean what? Is -1 ALWAYS for a failed auth, or is it for errors in general? If it is for errors in general, wouldn't you have to write code to handle auth errors versus other errors?

    ReplyDelete
  2. It is not just for failed auth. If there is any error then the code would be -1. It means that you can use it for all error handling. For example, if the channel that you are subscribing to is not specified in this.wschannels then this would result in an error and again code would be -1.

    ReplyDelete
  3. So how do you tell then? Are there further details in the event handler? 

    ReplyDelete
  4. It's just the code and the 'msg'. I get your point that errors may result from application setting or authorization or from any other source, then all refer to the same code and msg would contain the error message. Right now the errors are not categorized.

    ReplyDelete
  5. Is the msg something specific though? So one could do a string check? It isn't "clean" as some other number, but it's still possible.

    ReplyDelete
  6. As the application is built some checks can be added. If say the channel that you're trying to publish doesn't exist then msg would be 'Channel channel_name is not defined'. In case of authorization failure 'Access denied', for syntax error in Channel listener - 'Verify the syntax... '. Though this is not a complete list, but something can be built using string comparison.

    ReplyDelete

Post a Comment

Popular posts from this blog

Adding beforeRender and afterRender functions to a Backbone View

I was working on a Backbone application that updated the DOM when a response was received from the server. In a Backbone View, the initialize method would perform some operations and then call the render method to update the view. This worked fine, however there was scenario where in I wanted to perform some tasks before and after rendering the view. This can be considered as firing an event before and after the function had completed its execution. I found a very simple way to do this with Underscore's wrap method.

De-obfuscating javascript code in Chrome Developer Tools

I had blogged about JavaScript debugging with Chrome Developer Tools  some time back, wherein I have explained how these developer tools can help in debugging javascript code. Today Google Chrome 12 was released and my Chrome browser was updated to this version. As with every release, there have been some improvements made on performance, usability etc,. One feature that stood out for me is the ability to De-obfuscate the javascript code. What is Minification? Minification is the process of removing unnecessary characters such as white spaces, comments, new lines from the source code. These otherwise would be added to make the code more readable. Minifying the source code helps in reducing the file size and thereby reducing the time taken to download the file. This is the reason why most of the popular javascript libraries such as jQuery are minified. A minified jQuery file is of 31 KB in size where as an uncompressed one is about 229 KB. Unfortunately, debugging minified javascript f

On GraphQL and building an application using React Apollo

When I visualize building an application, I would think of using React and Redux on the front-end which talks to a set of RESTful services built with Node and Hapi (or Express). However, over a period of time, I've realized that this approach does not scale well when you add new features to the front-end. For example, consider a page that displays user information along with courses that a user has enrolled in. At a later point, you decide to add a section that displays popular book titles that one can view and purchase. If every entity is considered as a microservice then to get data from three different microservices would require three http  requests to be sent by the front-end app. The performance of the app would degrade with the increase in the number of http requests. I read about GraphQL and knew that it is an ideal way of building an app and I need not look forward to anything else. The GraphQL layer can be viewed as a facade which sits on top of your RESTful services o