Secure Your WebSocket JavaScript Client

Before you add security to your clients, follow the steps in Checklist: Secure Network Traffic with the Gateway and Checklist: Configure Authentication and Authorization to set up security on Kaazing Gateway for your client. The authentication and authorization methods configured on the Gateway influence your client security implementation. In this procedure, we provide an example of the most common implementation.

Before You Begin

This procedure is part of Checklist: Build JavaScript Clients Using Kaazing Gateway:

  1. Interact with Kaazing Gateway Using the WebSocket API
  2. Interact with Kaazing Gateway Using the EventSource API
  3. Migrate JavaScript Applications to Kaazing Gateway 4.x
  4. Secure Your JavaScript Client
  5. Display Logs for the JavaScript Client

To Secure Your WebSocket JavaScript Client

This procedure contains the following topics:

Authenticating your client involves implementing a challenge handler to respond to authentication challenges from the Gateway. If your challenge handler is responsible for obtaining user credentials, then you will also need to implement a login handler.

Creating a Basic Challenge Handler

A challenge handler is a constructor used in an application to respond to authentication challenges from the Gateway when the application attempts to access a protected resource. Each of the resources protected by the Gateway is configured with a different authentication scheme (for example, Basic, Application Basic, Application Negotiate, or Application Token), and your application requires a challenge handler for each of the schemes that it will encounter or a single challenge handler that will respond to all challenges. Also, you can add a dispatch challenge handler to route challenges to specific challenge handlers according to the URI of the requested resource.

For information about each authentication scheme type, see Configure the HTTP Challenge Scheme.

Clients with a single challenge handling strategy for all 401 challenges can simply set a specific challenge handler as the default using webSocketFactory.setChallengeHandler(). The following is an example of how to implement a single challenge handler for all challenges:

function setup(webSocketFactory) {
        // Configure a Basic Challenge Handler
        var basicHandler = new BasicChallengeHandler();

        basicHandler.loginHandler = function(callback) { 
                // Create static credentials for test
                var credentials = new PasswordAuthentication("joe", "welcome");
                callback(credentials); 
        }

        webSocketFactory.setChallengeHandler(basicHandler);
}

The preceding example uses static credentials, but you will want to create a login handler to obtain individual user credentials. Here is an example using a popup login dialog to respond to login challenges and obtain user credentials:

function setupSSO(webSocketFactory) {
        /* Respond to authentication challenges with popup login dialog */
        var basicHandler = new BasicChallengeHandler();
        basicHandler.loginHandler = function(callback) {
                popupLoginDialog(callback);
        }
        webSocketFactory.setChallengeHandler(basicHandler);
}

function popupLoginDialog(callback) {
    //popup dialog to obtain credentials
    var popup = document.getElementById("logindiv");
    popup.style.display = "block";
    var login = document.getElementById("login");
    var cancel = document.getElementById("cancel");

    //"OK" button was clicked, invoke callback function with credentials for login
    login.onclick = function() {
        var username = document.getElementById("username");
        var password = document.getElementById("password");
        var credentials = new PasswordAuthentication(username.value, password.value);
        //clear user input
        username.value = "";
        password.value = "";
        //hide popup
        popup.style.display = "none";
        callback(credentials);
    }

    //"Cancel" button has been clicked, invoke callback function with null argument to cancel login
    cancel.onclick = function() {
        var username = document.getElementById("username");
        var password = document.getElementById("password");
        //clear user input
        username.value = "";
        password.value = "";
        //hide popup
        popup.style.display = "none";
        callback(null);
    }
}

This example is taken from the out of the box JavaScript WebSocket Demo at http://localhost:8001/demo/. The code for the demo can be viewed in GATEWAY_HOME/demo/javascript/. For more information on challenge handlers and how to configure location-specific challenge handling strategies, see the JavaScript Client API.

To have the out of the box JavaScript WebSocket demo prompt you for authentication, open the Gateway configuration file (GATEWAY_HOME/conf/gateway-config.xml), remove the HTML comments surrounding the <authorization-constraint> child of the <echo> service, restart the Gateway, and then try the demo. You will be prompted for user credentials.

This BasicChallengeHandler can be instantiated using a new BasicChallengeHandler(). After instantiating BasicChallengeHandler, the loginHandler function can be implemented to handle the authentication challenge. By default, loginHandler will send an empty PasswordAuthentication.

                var basicHandler = new BasicChallengeHandler(); 
                basicHandler.loginHandler = function(callback) {
                        callback(new PasswordAuthentication("global", "credentials"));
                };
                webSocketFactory.setChallengeHandler(basicHandler);
                

Creating a Custom Challenge Handler

The ChallengeHandler object contains two methods, canHandle() and handle(). You create a custom challenge handler by using these methods to determine whether a challenge handler can handle the authentication scheme in the 401 response, and then passing the request to the challengeRequest object.

var myChallengeHander = function() {

  this.canHandle = function(challengeRequest) {
    // Return true if challengeRequest.authenticationScheme matches your scheme.
    return challengeRequest != null && "token" == challengeRequest.authenticationScheme.trim().toLowerCase();
  }
  this.handle = function(challengeRequest, callback) {
    var challengeResponse = null;
    if (challengeRequest.location != null) {
      var token = getToken();
      if (token != null) {
        // Set the token to challengeResponse
        challengeResponse = new ChallengeResponse("Token "+token, null);
      }
    }
    // Invoke callback function with challenge response
    callback(challengeResponse);
  }
}

The first time the user visits the web page containing your challenge handler, the user will not have the token. Your application can obtain the token using whatever method best suits your users and authentication process. For example, you could prompt the user for credentials or use a third-party token provider. Once the token is obtained, the token can be used for all subsequent visits to the page.

Once this function has determined that it can handle the challenge, it calls ChallengeRequest and a callback to handle the challenge. The ChallengeRequest is an immutable object representing the challenge presented by the server that contains a constructor from the protected URI location triggering the challenge, and an entire server-provided 'WWW-Authenticate:' string.

Once you have created a custom challenge handler, you can set it as the default challenge handler to be used for all HTTP requests.

webSocketFactory.setChallengeHandler = function(challengeHandler) {
  if (challengeHandler == null) {
    throw new Error("challengeHandler not defined");
  }
  webSocketFactory.setChallengeHandler(new myChallengeHander());
}

Managing Log In Attempts

When it is not possible for the Kaazing Gateway client to create a challenge response, the client must return null to the Gateway to stop the Gateway from continuing to issue authentication challenges.

The following example is modified code taken from the JavaScript demo (GATEWAY_HOME/demo/javascript/) and demonstrates how to stop the Gateway from issuing further challenges.

var maxRetries = 2;
var retry = 0;
function setupSSO(factory) {
  /* Respond to authentication challenges with popup login dialog */
  var basicHandler = new BasicChallengeHandler();
  basicHandler.loginHandler = function(callback) {
    if (retry++ >= maxRetries) {
      callback(null);       // abort authentication process if reaches max retries
    }
    else {
      popupLoginDialog(callback);
    }
  }
  factory.setChallengeHandler(basicHandler);
}
...

Registering Challenge Handlers at Locations

Client applications with location-specific challenge handling strategies can register a DispatchChallengeHandler object, on which location-specific ChallengeHandler objects are then registered. The result is that whenever a request matching one of the specific locations encounters a 401 challenge from the server, the corresponding ChallengeHandler object is invoked to handle the challenge. For example:

var basicHandler1 = new BasicChallengeHandler();
basicHandler1.loginHandler = function(callback) {
        popupLoginDialog(callback);
};

var basicHandler2 = new BasicChallengeHandler();
basicHandler2.loginHandler = function(callback) {
        callback(new PasswordAuthentication("username", "password"))
};

var dispatchHandler = new DispathChallengeHandler();
dispatchHandler.register("ws://myserver.example.com", basicHandler1);
dispatchHandler.register("ws://anotherserver.example.com", basicHandler2);

webSocketFactory.setChallengeHandler(dispatchHandler);

// Enable the ChallengeHandler by using this webSocketFactory to create a new WebSocket object
var websocket = webSocketFactory.createWebSocket("ws://myserver.example.com");

Using Wildcards to Match Sub-domains and Paths

You can use wildcards (“*”) when registering locations using DispatchChallengeHandler. Some examples of locationDescription values with wildcards are:

Negotiating a Challenge

A Negotiate challenge handler handles initial empty "Negotiate" challenges from the Gateway. It uses other candidate challenge handlers to assemble an initial context token to send to the Gateway, and is responsible for creating a challenge response that can delegate to the appropriate candidate.

In addition, you can register more specific NegotiableChallengeHandler objects with this initial NegotiateChallengeHandler to handle initial Negotiate challenges and subsequent challenges associated with specific Negotiation mechanism types and object identifiers.

Use DispatchChallengeHandler to register a NegotiateChallengeHandler at a specific location. The NegotiateChallengeHandler has a NegotiableChallengeHandler instance registered as one of the potential negotiable alternative challenge handlers.

var negotiableHandler = new NegotiableChallengeHandler();
var negotiableHandler.loginHandler  = function(callback) {...};
var negotiateHandler = new NegotiateChallengeHandler();
negotiateHandler.register(negotiableHandler);

webSocketFactory.setChallengeHandler(negotiateHandler);

Next Step

You have completed the JavaScript client examples.

See Also

JavaScript Client API