Just a second...

Example: Register an authentication handler

The following examples use the Diffusion™ API to register a control authentication handler with the Diffusion server . The examples also include a simple or empty authentication handler.

The name by which the control authentication handler is registered must be configured in the Server.xml configuration file of the Diffusion server for the control authentication handler to be called to handle authentication requests.

JavaScript
import * as diffusion from 'diffusion';

const PASSWORDS = {
    manager: 'password',
    guest: 'asecret',
    brian: 'boru',
    another: 'apassword'
};

/**
 * An example of a control authenticator.
 *
 * This shows a simple example using a table of permitted principals with
 * their passwords. It also demonstrates how the handler can change the
 * properties of the client being authenticated.
 */
const exampleAuthenticator = {
    authenticate: (principal, credentials, sessionProperties, proposedProperties, callback) => {
        const password = PASSWORDS[principal];

        if (password !== credentials) {
            if (principal === 'manager') {
                // manager allows all proposed properties
                callback.allow(proposedProperties);
            } else if (principal === 'brian') {
                // brian is allowed all proposed properties and also gets
                // the 'super' role added
                const result = { ...proposedProperties };
                const roles = diffusion.stringToRoles(sessionProperties.ROLES);
                roles.add('super');
                result.ROLES = diffusion.rolesToString(roles);
                callback.allow(result);
            } else {
                // all others authenticated but ignoring proposed properties
                callback.allow();
            }
        } else {
            // Any principal not in the table is denied.
            callback.deny();
        }
    },
    onClose: () => {
        console.log('The authenticator has disconnected');
    },
    onError: (err) => {
        console.log('An error occurred');
    }
};

/**
 * This is a control client which registers an authentication handler with a
 * Diffusion server.
 */
async function runExample() {
    const session = await diffusion.connect({
        principal : 'admin',
        credentials: 'password'
    });

    session.security.setAuthenticator('custom-authenticator', exampleAuthenticator)
}
.NET
/**
 * Copyright © 2021 - 2023 DiffusionData Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using PushTechnology.ClientInterface.Client.Callbacks;
using PushTechnology.ClientInterface.Client.Factories;
using PushTechnology.ClientInterface.Client.Features.Control.Clients;
using PushTechnology.ClientInterface.Client.Security.Authentication;
using PushTechnology.DiffusionCore.Client.Types;
using static System.Console;
using PushTechnology.ClientInterface.Client.Session;
using System;

namespace PushTechnology.ClientInterface.Example {
    /// <summary>
    /// Implementation of a client which authenticates other sessions using
    /// a registered authentication handler.
    /// </summary>
    public sealed class AuthenticationControl{
        public async Task AuthenticationControlExample(string serverUrl) {
            // Connect as a control session
            var session = Diffusion.Sessions.Principal( "control" ).Password( "password" )
                .CertificateValidation((cert, chain, errors) => CertificateValidationResult.ACCEPT)
                .Open(serverUrl);

            WriteLine("Opening control session.");

            IRegistration registration = null;

            try
            {
                registration = await session.AuthenticationControl.SetAuthenticationHandlerAsync(
                    "before-system-handler", new Authenticator(), CancellationToken.None );

                WriteLine("Authentication handler registered. Authenticator created.");

                Diffusion.Sessions.Principal("client")
                    .Credentials(Diffusion.Credentials.Password("password"))
                    .CertificateValidation((cert, chain, errors) 
                        => CertificateValidationResult.ACCEPT)
                    .Open(serverUrl, new SessionOpenCallback());

                await Task.Delay(TimeSpan.FromMilliseconds(2000), CancellationToken.None );
            } catch ( TaskCanceledException ) {
                //Task was cancelled; 
            } finally {
                WriteLine("Closing control session.");

                await registration.CloseAsync();
                session.Close();
            }
        }

        /// <summary>
        /// Callback used when a session is opened using ISessionFactory.Open
        /// </summary>
        private sealed class SessionOpenCallback : ISessionOpenCallback
        {
            public void OnError(ErrorReason errorReason) 
                => WriteLine($"An error occurred: {errorReason}");

            public void OnOpened(ISession session)
            {
                WriteLine("Other session opened.");

                session.Close();

                WriteLine("Other session closed.");
            }
        }

        /// <summary>
        /// Basic implementation of the control authenticator.
        /// </summary>
        private sealed class Authenticator : IControlAuthenticator {
            /// <summary>
            /// Method which decides whether a connection attempt should be allowed, denied or
            /// if another authenticator should evaluate this request.
            /// </summary>
            /// <param name="principal">The session principal.</param>
            /// <param name="credentials">The credentials.</param>
            /// <param name="sessionProperties">The session properties.</param>
            /// <param name="proposedProperties">The client proposed properties.</param>
            /// <param name="callback">The callback.</param>
            public void Authenticate(
                string principal,
                ICredentials credentials,
                IReadOnlyDictionary<string, string> sessionProperties,
                IReadOnlyDictionary<string, string> proposedProperties,
                IAuthenticatorCallback callback ) {

                switch ( principal ) {
                case "admin": {
                        WriteLine( "Authenticator allowing connection with proposed properties." );
                        callback.Allow( proposedProperties );
                        break;
                    }
                case "client": {
                        WriteLine( "Authenticator allowing connection with no properties." );
                        callback.Allow();
                        break;
                    }
                case "block": {
                        WriteLine( "Authenticator denying connection." );
                        callback.Deny();
                        break;
                    }
                default: {
                        WriteLine( "Authenticator abstaining." );
                        callback.Abstain();
                        break;
                    }
                }
            }

            /// <summary>
            /// Notification of authenticator closure.
            /// </summary>
            public void OnClose() => WriteLine( "Authenticator closed." );

            /// <summary>
            /// Notification of error.
            /// </summary>
            /// <param name="errorReason">Error reason.</param>
            public void OnError( ErrorReason errorReason ) 
                => WriteLine( $"Authenticator received an error: {errorReason}" );
        }
    }
}
Java and Android
/*******************************************************************************
 * Copyright (C) 2023 DiffusionData Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.pushtechnology.diffusion.manual;

import com.pushtechnology.diffusion.client.Diffusion;
import com.pushtechnology.diffusion.client.callbacks.ErrorReason;
import com.pushtechnology.diffusion.client.features.control.clients.AuthenticationControl;
import com.pushtechnology.diffusion.client.features.control.clients.AuthenticationControl.ControlAuthenticator;
import com.pushtechnology.diffusion.client.session.AuthenticationException;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionFactory;
import com.pushtechnology.diffusion.client.types.Credentials;

import java.util.Map;

/**
 * This is a control client which registers an authentication handler with a
 * Diffusion server.
 * <P>
 * This uses the 'AuthenticationControl' feature.
 *
 * @author DiffusionData Limited
 */
public final class AuthenticationControlExample {

    public static void main(String[] args) {

        // create a SessionFactory and establish a control session
        final SessionFactory sessions = Diffusion.sessions();

        final Session controlSession = sessions
            .principal("control")
            .password("password")
            .open("ws://localhost:8080");

        // set our custom authenticator as the before-system-handler
        controlSession.feature(AuthenticationControl.class)
            .setAuthenticationHandler("before-system-handler", new MyAuthenticator()).join();

        // try to connect with a principal that is not allowed by our custom authenticator
        try {
            sessions.principal("client").open("ws://localhost:8080");
        }
        catch (AuthenticationException e) {
            System.out.println("This should fail: " + e.getMessage());
        }

        // connect with a principal that is allowed by our custom authenticator
        final Session session = sessions
            .principal("diffusion_client")
            .password("password")
            .open("ws://localhost:8080");

        System.out.printf("Connected as %s with id %s\n", session.getPrincipal(), session.getSessionId());

        session.close();
        controlSession.close();
    }

    private static class MyAuthenticator implements ControlAuthenticator {

        @Override
        public void authenticate(String principal, Credentials credentials,
            Map<String, String> sessionProperties,
            Map<String, String> proposedProperties,
            Callback callback) {

            // only allow connections from principals with the 'diffusion_' prefix
            if (!principal.startsWith("diffusion_")) {
                System.out.println("Principal does not begin with diffusion_ prefix. Connection Rejected.");
                callback.deny();
                return;
            }

            System.out.println("Principal begins with diffusion_ prefix. Connection Accepted.");
            callback.allow();
        }

        @Override
        public void onClose() { }

        @Override
        public void onError(ErrorReason errorReason) { }
    }
}
C
/**
 * Copyright © 2021 - 2023 DiffusionData Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <stdio.h>
#include <stdlib.h>

#ifndef WIN32
        #include <unistd.h>
#else
        #define sleep(x) Sleep(1000 * x)
#endif

#include "diffusion.h"

/*
 * Authentication handlers are registered with a name, which is typically specified in
 * Server.xml
 *
 * Two handler names are provided by default;
 *      - before-system-handler
 *      - after-system-handler
 *
 * Additional handlers may be specified for Diffusion through the Server.xml file
 * and an accompanying Java class that implements the AuthenticationHandler interface.
 *
 * This example will:
 *      - Deny all anonymous connections
 *      - Allow connections where the principal and credentials is in USERS
 *      - Abstain from all other decisions, thereby letting Diffusion and other
 *        authentication handlers decide what to do
 */
typedef struct user_credentials_s {
        const char *username;
        const char *password;
} USER_CREDENTIALS_T;


// Username/password pairs that this handler accepts.
static const USER_CREDENTIALS_T USERS[] = {
        { "manager", "password" },
        { "guest", "asecret" },
        { "brian", "boru" },
        { "another", "apassword" },
        { NULL, NULL }
};


static int on_authentication_handler_active(
        SESSION_T *session,
        const DIFFUSION_REGISTRATION_T *registered_handler)
{
        // authentication handler is now active
        return HANDLER_SUCCESS;
}


static int on_authentication_handler_error(const DIFFUSION_ERROR_T *error)
{
        // An error has occurred in the authentication handler
        return HANDLER_SUCCESS;
}


static void on_authentication_handler_close(void)
{
        // Authentication handler has been closed
}


static int on_registration_error(
        SESSION_T *session,
        const DIFFUSION_ERROR_T *error)
{
        // An error has occurred while registering the authentication handler
        return HANDLER_SUCCESS;
}


static int on_authenticate(
        SESSION_T *session,
        const char *principal,
        const CREDENTIALS_T *credentials,
        const HASH_T *session_properties,
        const HASH_T *proposed_session_properties,
        const DIFFUSION_AUTHENTICATOR_T *authenticator)
{
        if(principal == NULL || strlen(principal) == 0) {
                // Denying anonymous connection (no principal)
                diffusion_authenticator_deny(session, authenticator, NULL);
                return HANDLER_SUCCESS;
        }
        if(credentials == NULL) {
                // No credentials specified, abstaining
                // We're not an authority for this type of authentication so
                // abstain in case some other registered authentication handler can deal
                // with the request.
                diffusion_authenticator_abstain(session, authenticator, NULL);
                return HANDLER_SUCCESS;
        }
        if(credentials->type != PLAIN_PASSWORD) {
                // Credentials are not PLAIN_PASSWORD, abstaining
                diffusion_authenticator_abstain(session, authenticator, NULL);
                return HANDLER_SUCCESS;
        }

        char *password = calloc(credentials->data->len + 1, sizeof(char));
        memmove(password, credentials->data->data, credentials->data->len);

        int auth_decided = 0;
        int i = 0;
        while(USERS[i].username != NULL) {
                if(strcmp(USERS[i].username, principal) == 0 &&
                   strcmp(USERS[i].password, password) == 0) {
                        // Allow
                        diffusion_authenticator_allow(session, authenticator, NULL);
                        auth_decided = 1;
                        break;
                }
                i++;
        }

        if(auth_decided == 0) {
                // Abstain
                diffusion_authenticator_abstain(session, authenticator, NULL);
        }

        free(password);
        return HANDLER_SUCCESS;
}


int main(int argc, char** argv)
{
        const char *url = "ws://localhost:8080";
        const char *principal = "admin";
        const char *password = "password";

        CREDENTIALS_T *credentials = credentials_create_password(password);

        // Create a session, synchronously
        DIFFUSION_ERROR_T error = { 0 };
        SESSION_T *session = session_create(
                url,
                principal,
                credentials,
                NULL,
                NULL,
                &error);
        if (session == NULL) {
                fprintf(stderr, "TEST: Failed to create session\n");
                fprintf(stderr, "ERR : %s\n", error.message);
                return EXIT_FAILURE;
        }

        // create the authentication handler
        DIFFUSION_AUTHENTICATION_HANDLER_T handler = {
                .handler_name = "before-system-handler",
                .on_active = on_authentication_handler_active,
                .on_authenticate = on_authenticate,
                .on_error = on_authentication_handler_error,
                .on_close = on_authentication_handler_close
        };

        // set the authentication handler
        DIFFUSION_AUTHENTICATION_HANDLER_PARAMS_T params = {
                .handler = &handler,
                .on_error = on_registration_error
        };

        diffusion_set_authentication_handler(session, params);

        // Wait a while before closing the session
        sleep(5);

        // Close the session, and release resources and memory
        session_close(session, NULL);
        session_free(session);

        credentials_free(credentials);

        return EXIT_SUCCESS;
}

Change the URL from that provided in the example to the URL of the Diffusion server .