Secure Your Microsoft .NET Client

Before you add security to your clients, follow the steps in 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's security implementation. For information on secure network connections between clients and the Gateway, see Checklist: Secure Network Traffic with the Gateway.

Before You Begin

This procedure is part of Checklist: How to Build Microsoft .NET JMS Clients Using Kaazing Gateway, that includes the following steps:

  1. Use the Kaazing Gateway JMS Client Libraries for .NET
  2. Secure Your Microsoft .NET Client
  3. Display Logs for .NET JMS Clients
  4. Troubleshoot Your Microsoft .NET JMS Clients

To Secure Your Microsoft .NET Client

This section 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. For more information, see the .NET Client API.

Creating Basic and Application Basic Challenge Handlers

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.

The following is an example of how to implement a Basic/Application Basic challenge handler. The code is taken from the Kaazing .NET JMS Tutorial app here https://github.com/kaazing/dotnet.client.tutorials/blob/develop/jms/WindowsDesktop/StompDemoForm.cs. The ChallengeHandler is setup as follows:

namespace Kaazing.JMS.Demo
{
    /// <summary>
    /// Top level JMS Client Demo
    /// </summary>
    public partial class StompDemoForm : Form
    {
        private IConnection connection = null;
        private ISession session = null;
        private IMessageConsumer consumer = null;
        private IDictionary<String, List<IMessageConsumer>> consumers = null;
        BasicChallengeHandler basicHandler;

        private delegate void InvokeDelegate();
        private delegate void InvokeDelegate1();

        /// <summary>
        /// JMS Demo Form constructor
        /// </summary>
        public StompDemoForm()
        {
            InitializeComponent();

            String defaultLocation = "wss://demos.kaazing.com/jms";
            LocationText.Text = defaultLocation;

            ResourceManager resourceManager = new ResourceManager("Kaazing.JMS.Demo.StompDemoForm", GetType().Assembly);
            this.Icon = (Icon)resourceManager.GetObject("Favicon.Ico");           

            //setup ChallengeHandler to handler Basic/Application Basic authentications
            basicHandler = BasicChallengeHandler.Create();
            basicHandler.LoginHandler = new LoginHandlerDemo(this);
            
        }

Later in the code, when the program attempts to connect to the Gateway, it will set the ChallengeHandler for the connection:

private void JMS_Connect()
        {
            // Immediately disable the connect button
            ConnectButton.Enabled = false;
            LocationText.Enabled = false;
            Log("CONNECT:" + LocationText.Text);

            try
            {
                StompConnectionFactory connectionFactory = new StompConnectionFactory(new Uri(LocationText.Text));
                ChallengeHandlers.Default = basicHandler;   // set challenge handler
                ...

Clients with a single challenge handling strategy for all 401 challenges can simply set a specific challenge handler as the default using ChallengeHandlers.Default.

You will note that in the first code sample, basicHandler.LoginHandler is set using the object LoginHandlerDemo(). LoginHandlerDemo() is used to collect credentials and an instance is set in LoginHandlerDemo.cs:

using System;
using Kaazing.Security;

namespace Kaazing.JMS.Demo
{


    ///
    /// Challenge handler for Basic authentication. See RFC 2617.
    ///
    public class LoginHandlerDemo : LoginHandler
    {
        private StompDemoForm parent;
        /// <summary>
        /// constructor
        /// <para>pass in main form for callback</para>
        /// </summary>
        /// <param name="form"></param>
        public LoginHandlerDemo(StompDemoForm form)
        {
            this.parent = form;
        }


        #region LoginHandler Members

        PasswordAuthentication LoginHandler.GetCredentials()
        {
            return parent.AuthenticationHandler();
        }

        #endregion
    }
}

The AuthenticationHandler() method will be used by a function in the main program in StompDemoForm.cs.

Now let's look at the following files that collect user credentials in the Kaazing .NET JMS Tutorial app at https://github.com/kaazing/dotnet.client.tutorials/tree/develop/jms/WindowsDesktop:

Back in the main program file, StompDemoForm.cs, the AuthenticationHandler() method from LoginHandlerDemo.cs is used to call a new LoginDemoForm(), display the popup dialog with ShowDialog(), obtain the user credentials from Username and Password and return the submitted credentials to the Gateway.

///
/// Handle server authentication challenge request,
/// Popup a login window for username/password
///
public PasswordAuthentication AuthenticationHandler()
{
    PasswordAuthentication credentials = null;
    LoginDemoForm loginForm = new LoginDemoForm();
    AutoResetEvent userInputCompleted = new AutoResetEvent(false);

    //
    //Popup login window on new Task. Note: please use Task to do parallel jobs
    //
    Task t = Task.Factory.StartNew(() =&gt;
    {
        if (loginForm.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            credentials = new PasswordAuthentication(loginForm.Username, loginForm.Password.ToCharArray());
        }
        userInputCompleted.Set();
    });

    // wait user click OK or Cancel on login window
    userInputCompleted.WaitOne();
    return credentials;
}

To make a Gateway service issue a challenge to a client for authentication, in the Gateway configuration file, both the realm and authorization-constraint elements must be enabled, and a LoginModule must be used.

<service>
  <name>JMS Service</name>
  <type>jms</type>
  ...
  <realm-name>demo</realm-name>

  <authorization-constraint>
    <require-role>AUTHORIZED</require-role>
  </authorization-constraint>
</service>
...
<security>
...
  <realm>
    <name>demo</name>
    <description>Kaazing WebSocket Gateway Demo</description>

    <authentication>
      <http-challenge-scheme>Application Basic</http-challenge-scheme>
      <authorization-timeout>1800</authorization-timeout>
      <login-modules>
        <login-module>
          <type>file</type>
          <success>required</success>
          <options>
            <file>jaas-config.xml</file>
          </options>
        </login-module>
      </login-modules>
    </authentication>
  </realm>
  ...

For more information, see Checklist: Configure Authentication and Authorization.

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 .NET example demonstrates how to stop the Gateway from issuing further challenges.

public delegate void InvokeDelegate();

private const int maxRetries = 2;    // max retry number on wrong credentials
private int retry = 0;               // number of retry

/// <summary>
/// .Net HTML5 Demo Form
/// </summary>

public PasswordAuthentication AuthenticationHandler()
{
  PasswordAuthentication credentials = null;
  if (retry++ >= maxRetries)
  {
    return null;          // stop authentication when max retries reached
  }
  AutoResetEvent userInputCompleted = new AutoResetEvent(false);
  this.BeginInvoke((InvokeDelegate)(() =>
  {
    {
      credentials = new PasswordAuthentication(loginForm.Username, loginForm.Password.ToCharArray());
    }
    else
    {
      //user click cancel button to stop the authentication
      retry = 0;     // reset retry counter
      credentials = null;  // return null to stop authentication process
    }
    userInputCompleted.Set();
  }));
  // wait user click 'OK' or 'Cancel' on login window
  this.BeginInvoke((InvokeDelegate)(() =>
  {
    Log("CONNECTED");

    retry = 0;     // reset retry counter
    DisconnectButton.Enabled = true;
    SendButton.Enabled = true;
  }));
  {
    Log("DISCONNECTED");

    retry = 0;     // reset retry counter
    ConnectButton.Enabled = true;
    DisconnectButton.Enabled = false;
    SendButton.Enabled = false;

Negotiate and Register a Location-Specific Challenge Handler

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.

The following example creates a challenge handler to handle authentications and uses DispatchChallengeHandler to assign different challenge handler to each location:

dispatchHandler =
        ChallengeHandlers.Load<DispatchChallengeHandler>(typeof(DispatchChallengeHandler));

ChallengeHandlers.Default = dispatchHandler;

LoginHandler loginHandler = new LoginHandlerDemo(this);

Next, set a loginHandler for this location:

BasicChallengeHandler basicHandler =
        ChallengeHandlers.Load<BasicChallengeHandler>(typeof(BasicChallengeHandler));

basicHandler.LoginHandler = loginHandler;

dispatchHandler.Register("ws://myserver.com/*", basicHandler);

Add another challenge handler for a second location:

BasicChallengeHandler basicHandler2 =
        ChallengeHandlers.Load<BasicChallengeHandler>(typeof(BasicChallengeHandler));

basicHandler2.LoginHandler = loginHandler2;

dispatchHandler.Register("ws://otherserver.com/*", basicHandler2);

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:

See Also

.NET Client API