Diffusion and Diffusion Cloud Security: Authentication Providers

This is the first in a series of articles covering security features first introduced in Diffusion 5.0, and extended in later releases. I’ll assume Diffusion 5.5 or later because of the declarative authorization features added in that release. Most of what you will read here also applies to Diffusion Cloud.

The focus of this article is how to control who has access to a Diffusion server using Diffusion authentication handlers. The Unified API AuthenticationControl feature allows a control client session to authenticate other client sessions. I’ll show how to set up a simple authentication handler local to the server, then extend the example to deploy the handler as an AuthenticationControl client.

First let’s see how a client authenticates against a server. Diffusion allows a principal name and supporting credentials to be provided when a session is first established. The following example uses the Diffusion Java Unified API:

Session session = Diffusion.sessions()
  .principal("Bob").password("s3cr3t")
  .open("wss://diffusion.acme.com");

This creates an initial connection to a Diffusion server or Diffusion Cloud service using the WebSocket protocol, passing the principal name “Bob” and the password “s3cr3t” as credentials. If the server accepts the principal and credentials, it will create a new session; otherwise the new connection will be rejected. The principal “Bob” remains associated with the session until it is closed, or the principal is changed using Security.changePrincipal().

Terminology Diffusion uses the term “principal” for any entity that can be authenticated. A principal can be an individual person or some system component that interacts with Diffusion, hence the term is more general than “user”. Similarly, the term “credentials” is used for the proof material used to authenticate a principal. The example above uses a password, the most common type of credentials.

Authentication Handlers

How does a server determine whether to accept a pair of principal and credentials? It consults the registered authentication handlers. An authentication handler is a user-written or system-provided component that can accept or reject an authentication request.

Diffusion and Diffusion Cloud provide a system authentication handler which stores known principals in a local database. The default configuration for Diffusion is set up for developer convenience and is quite open. In particular, there are several principals, each with the same well-known password. The configuration should be changed before putting the system into production. Diffusion Cloud is a little different – system authentication is configured through the Diffusion Cloud dashboard during the provisioning process. In either case, the system authentication handler is just another authentication handler and can be combined with user-written authentication handlers.

The system authentication handler is a convenient way to authenticate Diffusion administrators and operators, but for a large user population we recommend an external identity store. Writing your own authentication handler allows you to integrate Diffusion authentication with other identity stores such as LDAP, and to customize authentication to support other types of credentials. You can also associate arbitrary data with authenticated sessions using Diffusion’s session properties feature.

A list of authentication handlers is registered in the server configuration. When establishing a new session, the server calls the authentication handlers in order, supplying the provided principal, credentials, and information about the proposed session. Here’s the AuthenticationHandler interface:

public interface AuthenticationHandler {

    void authenticate(
        String principal,
        Credentials credentials,
        SessionDetails sessionDetails,
        Callback callback);

    interface Callback {
        void allow();
        void allow(AuthenticationResult result);
        void abstain();
        void deny();
    }
}

The job of each authentication handler is to validate the provided information and produce a decision by calling one of the callback methods. It can allow the request, deny the request, or abstain. The server calls each handler in turn until one allows or denies the request. If all of the handlers abstain, then the request is denied.

An authentication handler that allows the request can optionally provide a set of roles to grant to the session, as well as session properties to associate with to the session. Roles are part of Diffusion’s declarative authorization and control the permissions the session has to perform actions and read data. Session properties are key/value pairs of strings that can contain arbitrary application data.

Knowing this, we can quickly knock up an implementation that accepts Bob. I’ve left out import and debug logging statements. The full code can be found on GitHub.

package com.pushtechnology.blog.security;

// ...

public class BobAuthenticationHandler implements AuthenticationHandler {

    private static final AuthenticationResult ROLES =
        authenticationResult().withRoles("AUTHENTICATION_HANDLER");

    public void authenticate(
        String principal,
        Credentials credentials,
        SessionDetails sessionDetails,
        Callback callback) {

        final String password =
            new String(credentials.toBytes(), Charset.forName("UTF-8"));

        if ("Bob".equals(principal) && "s3cr3t".equals(password)) {
            callback.allow(ROLES);
        }
        else {
            callback.abstain();
        }
    }
}

If the correct principal and credentials are provided, the authentication handler allows the session to be established, and grants the session the AUTHENTICATION_HANDLER role. The AUTHENTICATION_HANDLER role allows a session established using the “Bob” principal to register control authentication handlers. We’ll need this later on.

A production-grade handler would properly validate the password and be backed by a database supporting many users. For example, it might delegate the request by making an authentication check against an LDAP service.

AuthenticationHandler is an asynchronous interface. You can return from the authenticate method and invoke the callback in a different thread, which is useful if you need to make a network call to another service. Whether you respond immediately or asynchronously, always call one of the callback methods. If you don’t, the server will wait until the configured connection timeout (the default value is 2 seconds) before rejecting the connection request.

BobAuthenticationHandler is a local authentication handler, configured and deployed to the server. Consequently, it can’t be used with Diffusion Cloud. If you are a Diffusion Cloud user, you can skip on to the next section that discusses control authentication handlers, which are deployed in clients.

Compile the example (mvn clean package) and place the resulting jar file (target/authentication-handlers-1.0.jar) in the classpath of your Diffusion server. A simple way to do this is to copy the jar file to the server’s ext directory. If your server is running, stop it. Now edit the etc/Server.xml file; find the <security> section and edit it so it looks like this:

<security>
  <authorisation-handler-class></authorisation-handler-class>

  <authentication-handlers>
    <authentication-handler class =
      "com.pushtechnology.blog.security.BobAuthenticationHandler"/>

    <!-- Default configuration -->
    <control-authentication-handler handler-name="before-system-handler"/>
    <system-authentication-handler/>
    <control-authentication-handler handler-name="after-system-handler"/>
  </authentication-handlers>
</security>

I’ve added the authentication handler before the out-of-the-box configuration. Things get more interesting when multiple handlers are configured. The position of an authentication handler in the configured list determines its relative priority. In our modified configuration, the BobAuthenticationHandler will be consulted first, followed by the before-system-handler control handler, the system authentication handler, and the after-system-handler. The remaining handlers will only be consulted if the BobAuthenticationHandler returns abstain.

Now restart the server. The GitHub repository contains a test client that you can use to check the authentication handler works.

% java -jar target/testclient.jar ws://localhost:8080 Bob s3cr3t
Principal 'Bob' was authenticated by the server.

% java -jar target/testclient.jar ws://localhost:8080 Bob password
Principal 'Bob' was rejected by the server.

Control Authentication Handlers

Diffusion provides several APIs for remotely controlling the behavior of a server from a control client. Let’s take advantage of one of the APIs to add another authentication handler to our server that runs as a client application, separate to the server. This authentication handler can be used with either Diffusion or Diffusion Cloud.

Another benefit of running outside the server is the choice of implementation language. You can write such an authentication handler using Java, C#, or C. This opens up many integration possibilities, but to keep the discussion simple I’ll continue to use Java.

Here’s what it looks like.

package com.pushtechnology.blog.security;

// ...

public class AliceAuthenticationHandler implements ControlAuthenticationHandler {

    private static final AuthenticationResult ROLES =
        authenticationResult().withRoles("CLIENT");

    public void authenticate(
        String principal,
        Credentials credentials,
        SessionDetails sessionDetails,
        Callback callback) {

        final String password =
            new String(credentials.toBytes(), Charset.forName("UTF-8"));

        if ("Alice".equals(principal) && "0penup".equals(password)) {
            callback.allow(ROLES);
        }
        else {
            callback.abstain();
        }
    }

    public void onActive(RegisteredHandler registeredHandler) {
        System.out.printf("%s registered.%n", getClass().getSimpleName());
    }

    public void onClose() {
        System.out.printf("%s closed.%n", getClass().getSimpleName());
    }
}

Note the similarity to the BobAuthenticationHandler. I’ve changed the handler to implement ControlAuthenticationHandler. This interface extends AuthenticationHandler, adding two additional life cycle callbacks that are notified when the handler is registered with the server and closed by the server. You might use these callbacks to establish and close a connection to a database.

A further difference to BobAuthenticationHandler is that this handler grants the less privileged role CLIENT to the sessions that it authenticates.

Let’s now add a main method to the handler that connects to the server and registers the handler. The key lines are as follows (see the GitHub repository for the full source).

public class AliceAuthenticationHandler implements ControlAuthenticationHandler {

    // ...

    public static void main(String... args) throws IOException {

        // ...

        final Session session = Diffusion.sessions()
            .principal(principal)
            .password(password)
            .open(url);

        final AuthenticationControl authenticationControl =
            session.feature(AuthenticationControl.class);

        authenticationControl.setAuthenticationHandler(
            "after-system-handler",
            allOf(SessionDetails.DetailType.class),
            new AliceAuthenticationHandler());

        // ...
    }
}

Here I establish a session with the server, acquire the AuthenticationControl feature, and register a new instance of the handler. The first two parameters to setAuthenticationHandler are interesting.

The first parameter is the “handler name” – there must be a corresponding entry in the server configuration or the registration will fail. The example uses after-system-handler, which is present in the default configuration for both Diffusion and Diffusion Cloud.

You can increase availability by deploying several control authentication handlers, all registered for the same handler name. If there are several control authentication handlers registered for the same name, the server will select a single handler to process an authentication request. Selection is done on a round-robin basis, with a new handler chosen for each request. If there is currently no handler registered for the handler name, the server will skip to the next configured handler to process the request.

The second parameter filters the level of detail about authenticating clients that will be provided to the authenticate method. Example details include the transport type, IP address, and resolved location such as latitude and longitude. Requesting fewer details means less data will be transferred over the network, so the general rule is if you don’t need it, don’t ask for it. Here we’re greedy and ask for all available details.

Now we can start the control authentication handler client. Like any client, it must also authenticate with the server. With our modified Diffusion configuration, we can make use of the account provided by the configured BobAuthenticationHandler.

% java -jar target/control-authentication-handler.jar ws://localhost:8080 Bob s3cr3t
Connected to Diffusion at ws://localhost:8080
Press any key to exit.
AliceAuthenticationHandler registered.

Diffusion Cloud doesn’t allow the server configuration to be changed. Instead, we can use system authentication handler. If you are using Diffusion Cloud, use the dashboard to add a system user, and use its principal and credentials instead of Bob/s3cr3t. You’ll also need to add the system user to the AUTHENTICATION_HANDLER role. Finally, make sure you use the correct URL for your server.

Can we now authenticate as Alice? Open up a new terminal window and try the following:

% java -jar target/testclient.jar ws://localhost:8080 Alice 0penup
Principal 'Alice' was authenticated by the server.

Yes, we can!

Implementation considerations

Anonymous browsing

Client sessions may not provide a principal name when connecting. Such clients are known as “anonymous”. It’s common to provide anonymous clients with some basic access to your application, and provide the choice to log-in with a real account if they want full access. This can be achieved using the changePrincipal method of the Diffusion Security feature.

Authentication handlers are still called to authenticate anonymous clients, with the principal name set to the empty string. An authentication handler can choose whether to allow or deny such clients.

Restricting access to control functionality

In Diffusion 5.0 and 5.1, a client that can establish a Unified API connection to the server can access control client functionality, and hence register a control authentication handler. The server delegates trust to such clients, so it is important that an attacker cannot abuse these features. If you are using these releases, be careful how you expose the ability to make a Unified API connection. The default Diffusion configuration has a separate connector enabled on port 8081 for Unified API connections. You should deploy firewalls to prevent this port from being exposed directly to the Internet, or disable the connector if you don’t need control client features.

Diffusion Cloud and Diffusion provide role-based authorization. This allows fine grained control over what a client can do based on their authenticated principal. For example, the server can be configured to allow only ‘Bob’ to register a control authentication handler. We’ll look further at role-based authorization in a later article.

TLS (SSL)

TLS is the protocol formerly (and more commonly) known as SSL. All Diffusion transport protocols support TLS. To ensure that passwords and session tokens are not transferred in the clear, we recommend you use TLS to protect all communication with the server.

Finishing up

This completes our introduction to authentication handlers. My next article will cover configuring system authentication in more detail.


Further reading

The Diffusion Data logo

BLOG

React PubSub using Diffusion Websocket Server

July 08, 2024

Read More about React PubSub using Diffusion Websocket Server/span>

The Diffusion Data logo

BLOG

Creating a WebSocket Server for PubSub

June 28, 2024

Read More about Creating a WebSocket Server for PubSub/span>

The Diffusion Data logo

BLOG

100 million updates per second - Landmark Diffusion cluster performance

July 02, 2024

Read More about 100 million updates per second - Landmark Diffusion cluster performance/span>