Search This Blog

Sunday, December 7, 2014

Simple Authentication when Connecting Service

Simple example showing how to implement authentication when connecting a service.


The source code for this example can be downloaded here.

Introduction

The example bellow demonstrates how to implement the communication where clients need to authenticate in order to use a service. To demonstrate this scenario the example shows a simple client-service communication. The client application opens the authenticated connection and then sends 'Hello service' message. The service application authenticate every connected client and when it receives a message it just simply responses with 'Hello client'.

To Run Example

  1. Download Eneter for .NET.
  2. Download this example and open the solution in Visual Studio.
  3. Add Eneter.Messaging.Framework.dll library to 'References' in both projects inside the solution.
  4. Build the solution.
  5. Run HelloService.
  6. Run HelloClient.

Authenticated Connection

The authenticated connection is the connection which verifies the identity of communicating parts. (If they are who they are saying they are.) The identities can be checked on the protocol level e.g. using TLS (Transport Layer Security) or its predecessor SSL (Secure Socket Layer) but this approach may not be always suitable.
E.g. if a server application provides multiple services on one IP address and port and some services requires client identity verification and some not.
Or if you need the identity verification but you do not want to encrypt the whole communication (e.g. because of performance reasons).
In such cases you may need to implement own identity verification. To do so you can use authentication functionality from Eneter Messaging Framework.

The Eneter authentication implemented in the example bellow performs the following sequence:
  1. The client opens the connection and sends the login message.
  2. The service receives the login message and tries to find the client and its password in the database. Then it uses the client password from the database and encrypts the generated GUID and sends it as the handshake message.
  3. The client receives the handshake message and decrypts it with own password.
    (Note: if the service identity is correct then the handshake message is encrypted with correct password and so the client can decrypt it.)
    Then the client takes the originaly encrypted handshake message and encrypts it again and sends it to the service as the handshake response message.
  4. The service receives the handshake response message and decrypts it two times. If the result is the original GUID then the identity is considered verified and it sends the acknowledge message that the connection is established.
  5. The client receives the acknowledge message and the connection is finally open.
    Now both sides can communicate with messages which are not encrypted.
Note: to improve the security you may consider to store (in database) and use password hash instead of real passwords.

Service Application

The service is a simple console application which listens to connecting clients. When a client is opening the connection it performs the authentication sequence described above.
Then when the connection is open it receives 'Hello service' message and responses 'Hello client' message.

The whole implementation is very simple:

using System;
using System.Collections.Generic;
using Eneter.Messaging.DataProcessing.Serializing;
using Eneter.Messaging.EndPoints.StringMessages;
using Eneter.Messaging.MessagingSystems.Composites.AuthenticatedConnection;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;

namespace HelloService
{
    class Program
    {
        private static Dictionary<string, string> myUsers = new Dictionary<string, string>();
        private static IDuplexStringMessageReceiver myReceiver;

        static void Main(string[] args)
        {
            //EneterTrace.TraceLog = new StreamWriter("d:/tracefile.txt");

            // Simulate database with users.
            myUsers["John"] = "password1";
            myUsers["Steve"] = "password2";

            // Create TCP based messaging.
            IMessagingSystemFactory aTcpMessaging = new TcpMessagingSystemFactory();

            // Use authenticated connection.
            IMessagingSystemFactory aMessaging = 
                new AuthenticatedMessagingFactory(aTcpMessaging, GetHandshakeMessage, Authenticate);
            IDuplexInputChannel anInputChannel = 
                aMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:8092/");

            // Use simple text messages.
            IDuplexStringMessagesFactory aStringMessagesFactory = new DuplexStringMessagesFactory();
            myReceiver = aStringMessagesFactory.CreateDuplexStringMessageReceiver();
            myReceiver.RequestReceived += OnRequestReceived;

            // Attach input channel and start listening.
            // Note: the authentication sequence will be performed when
            //       a client connects the service.
            myReceiver.AttachDuplexInputChannel(anInputChannel);

            Console.WriteLine("Service is running. Press Enter to stop.");
            Console.ReadLine();

            // Detach input channel and stop listening.
            // Note: tis will release the listening thread.
            myReceiver.DetachDuplexInputChannel();
        }

        private static void OnRequestReceived(object sender, StringRequestReceivedEventArgs e)
        {
            // Handle received messages here.
            Console.WriteLine("Received message: " + e.RequestMessage);

            // Send back the response.
            myReceiver.SendResponseMessage(e.ResponseReceiverId, "Hello client");
        }

        // Callback which is called when a client sends the login message.
        // It shall verify the login and return the handshake message.
        private static object GetHandshakeMessage(string channelId,
                                                  string responseReceiverId,
                                                  object loginMessage)
        {
            // Find the login name and password in "database"
            // and encrypt the handshake message.
            if (loginMessage is string)
            {
                string aLoginName = (string)loginMessage;

                Console.WriteLine("Received login: " + aLoginName);

                if (myUsers.ContainsKey(aLoginName))
                {
                    string aPassword = myUsers[aLoginName];
                    ISerializer aSerializer = new AesSerializer(aPassword);
                    object aHandshakeMessage = aSerializer.Serialize<string>(Guid.NewGuid().ToString());

                    return aHandshakeMessage;
                }
            }
            
            // Login was not ok so there is not handshake message
            // and the connection will be closed.
            Console.WriteLine("Login was not ok. The connection will be closed.");
            return null;
        }

        // Callback which is called when a client sends the handshake response message.
        private static bool Authenticate(string channelId,
                                                string responseReceiverId,
                                                object loginMessage,
                                                object handshakeMessage,
                                                object handshakeResponseMessage)
        {
            if (loginMessage is string)
            {
                // Get the password associated with the user.
                string aLoginName = (string) loginMessage;
                string aPassword = myUsers[aLoginName];
                
                // Decrypt the handshake response message.
                // Handshake response message is one more time encrypted handshake message.
                // Therefore if the handshake response is decrypted two times it should be
                // the originaly generated GUID.
                try
                {
                    ISerializer aSerializer = new AesSerializer(aPassword);

                    // Decrypt handshake response to get original GUID.
                    object aDecodedHandshakeResponse1 = aSerializer.Deserialize<byte[]>(handshakeResponseMessage);
                    string aDecodedHandshakeResponse2 = aSerializer.Deserialize<string>(aDecodedHandshakeResponse1);

                    // Decrypt original handshake message.
                    string anOriginalGuid = aSerializer.Deserialize<string>(handshakeMessage);

                    // If GUIDs are equal then the identity of the client is verified.
                    if (anOriginalGuid == aDecodedHandshakeResponse2)
                    {
                        Console.WriteLine("Client authenticated.");

                        // The handshake response is correct so the connection can be established.
                        return true;
                    }
                }
                catch (Exception err)
                {
                    // Decoding of the response message failed.
                    // The authentication will not pass.
                    Console.WriteLine("Decoding handshake message failed.", err);
                }
            }
            
            // Authentication did not pass.
            Console.WriteLine("Authentication did not pass. The connection will be closed.");
            return false;
        }
    }
}


Client Application

The client is a simple console application which tries to opens the authenticated connection using the sequence described above. Once the connection is open it sends the 'Hello service' message and receives 'Hello client' message.

The whole implementation is very simple:

using System;
using Eneter.Messaging.DataProcessing.Serializing;
using Eneter.Messaging.EndPoints.StringMessages;
using Eneter.Messaging.MessagingSystems.Composites.AuthenticatedConnection;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;

namespace HelloClient
{
    class Program
    {
        private static IDuplexStringMessageSender mySender;

        static void Main(string[] args)
        {
            // TCP messaging.
            IMessagingSystemFactory aTcpMessaging = new TcpMessagingSystemFactory();

            // Use authenticated connection.
            IMessagingSystemFactory aMessaging = 
                new AuthenticatedMessagingFactory(aTcpMessaging, GetLoginMessage, GetHandshakeResponseMessage);
            IDuplexOutputChannel anOutputChannel = 
                aMessaging.CreateDuplexOutputChannel("tcp://127.0.0.1:8092/");
        
            // Use simple text messages.
            mySender = new DuplexStringMessagesFactory().CreateDuplexStringMessageSender();
        
            // Subscribe to receive response messages.
            mySender.ResponseReceived += OnResponseMessageReceived;
        
            // Attach output channel and connect the service.
            // Note: this step performs the authentication sequence.
            //       If the authentication is successful then the connection is open.
            mySender.AttachDuplexOutputChannel(anOutputChannel);
        
            // Send a message.
            // Note: this message will not be encrypted.
            mySender.SendMessage("Hello service");
        
            Console.WriteLine("Client sent the message. Press ENTER to stop.");
            Console.ReadLine();
        
            // Detach output channel and stop listening.
            // Note: it releases the tread listening to responses.
            mySender.DetachDuplexOutputChannel();
        }

        private static void OnResponseMessageReceived(object sender, StringResponseReceivedEventArgs e)
        {
            // Process the incoming response here.
            Console.WriteLine("Received response: " + e.ResponseMessage);
        }

        public static object GetLoginMessage(string channelId, string responseReceiverId)
        {
            return "John";
        }

        public static object GetHandshakeResponseMessage(string channelId,
                                                         string responseReceiverId,
                                                         object handshakeMessage)
        {
            try
            {
                ISerializer aSerializer = new AesSerializer("password1");

                // Decrypt the hanshake message.
                // If the service identity is correct then the handshake message
                // is encrypted with correct client password and it is possible to decrypt it.
                // If not the exception is thrown.
                aSerializer.Deserialize<string>(handshakeMessage);

                // Take the original handshake message and encrypt it.
                object aHandshakeResponse = aSerializer.Serialize<byte[]>((byte[])handshakeMessage);
                
                return aHandshakeResponse;
            }
            catch (Exception err)
            {
                Console.WriteLine("Processing handshake message failed. The connection will be closed.", err);
            }
            
            return null;
        }
    }
}

No comments:

Post a Comment