Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Improving WCF Service Quality (Part 3 - Authentication & Authorization)

0.00/5 (No votes)
28 Feb 2022CPOL4 min read 3.7K  
How to add authentication and authorization to WCF services
This is Part 3 of a 4 part series of articles that discuss how to improve WCF services with Authentication, Authorization, Validation and Logging with Aspect Oriented Programming. This article covers authentication and authorization.

Introduction

We have discussed how to add validation in the previous part here. This part explains how I added Authentication and Authorization to WCF services.

Using the Code

To introduce Authentication and Authorization process to our service, we are going to use Aspect Oriented Programming concept and as explained in the first chapter, we are going to use PostSharp for this purpose.

At the first step, let's create the following new Class Library project and name it WCFService.Extensions. This project will reference WCFLibrary.Shared and will be referenced by all the Service Libraries. Next thing to do is add our class ServiceMethodAspect to the new Class Library project.

After adding the ServiceMethodAspect class, we are going to inherit our class from OnMethodBoundaryAspect class and we will decorate our class with three overridden methods, OnEntry, OnExit and OnException respectively.

C#
public class ServiceMethodAspect : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
    }
    public override void OnExit(MethodExecutionArgs args)
    {
    }
    public override void OnException(MethodExecutionArgs args)
    {
    }
}

When you do this, you will see that OnMethodBoundaryAspect class is not recognized. So, head over to NuGet Package Manager console and install PostSharp and PostSharp.Redist library and add a reference to PostSharp.Aspects namespace.

PostSharp libraries : NuGet

At this point, you won't be able to build your project anymore because PostSharp requires a valid license to be used in a project and we don't have a license yet. You can get a free license from this page if you meet the requirements. Also, deploying the license is described in this page. But the easiest way (if you don't mind installing an toolset to your Visual Studio) is to visit this page, install the extension and that's all. License will be automatically deployed and you will be able to build your application again.

Now what will these two method do? As the name suggests, OnEntry method will cover operations that will be performed prior to method execution. This method will be hit as soon as your service method is called, before executing anything related to your service. So this looks like a good place to check authentication and authorization. But you will realize that we don't have any information about the request object yet. This makes quite a lot of sense because our aspect does not know what method will be hit with which parameters. So, we should find a way to access our input parameters. As a start, let's get writing the following codes at the beginning of the OnEntry method.

C#
base.OnEntry(args);
string username = "";
string password = "";
Arguments methodArgs = args.Arguments;
foreach (var arg in methodArgs)
{
    if (arg as BaseRequest!= null)
    {
        username = (arg as AuthenticationObject).Username;
        password = (arg as AuthenticationObject).Password;
    }
}

What this piece of code does is it gets the arguments which the method has been invoked by calling args.Arguments and seeks for Username and Password properties in objects of type BaseRequest to fetch its values.

After this code is executed, we now have the values for the Username and Password properties within the input parameters. I think this makes sense for what we did in the first chapter (creating request classes for each and every method as an input parameter and inheriting all request classes from a base class which guarantees to have Username and Password property in each method's request class as input. After having necessary values, we now can check for the authentication first, then authorization, if authenticated then return results.

Now we have discovered that we have to return a proper response we now have to discover how. Again, referencing to the first chapter, remember that we have changed all our methods return response classes which inherits BaseResponse class which have IsException, IsSuccess and Messages properties of types bool, bool and string[] respectively. To get access to our response object, we need to write the following code:

C#
Type returnType = ((System.Reflection.MethodInfo)args.Method).ReturnType;
object returnObj = Activator.CreateInstance(returnType);
MethodBase mb = args.Method;

What does this code do? First, it fetches the method being executed by args.Methods and casts it to type System.Reflection.MethodInfo to discover the attributes of the method and provide access to method metadata, and finally get the return type of the method by accessing the ReturnType property. Now we are ready to check for authentication and authorization. So let's switch back to our WCFService.Utils project and add a folder named Auth and add two new classes named AuthenticationManager and AuthorizationManager respectively and decorate it as follows:

C#
public class AuthenticationManager
{
    public int? Authenticate(string username, string password, out string message)
    {
        message = null;
        // Do your authentication work here and return UserId if authenticated, null otherwise.
    }
}
C#
public class AuthorizationManager
{
    public bool IsAuthorized(int userId, string FullyQualifiedServiceName, string MethodName)
    {
        // Do your authorization work here according to the UserId, ServiceName and 
        // ServiceMethod name and return TRUE if authenticated, FALSE otherwise
    }
}

The code work within the Authenticate and IsAuthorized methods are intentionally blank to allow users to implement their own authentication and authorization process. This might use local database, cloud database, JSON file, text file, registry record, anything you would like. Just in case, bear in mind that an integer type identifier is declared to hold User data. According to your implementation, this type might also change according to your choice.

Now as we have described all parts necessary, let's get all pieces together and make authentication and authorization process work.

C#
public override void OnEntry(MethodExecutionArgs args)
{
    base.OnEntry(args);
    string username = "";
    string password = "";
    Arguments methodArgs = args.Arguments;
    foreach (var arg in methodArgs)
    {
        if (arg as BaseRequest != null)
        {
            username = (arg as BaseRequest).Username;
            password = (arg as BaseRequest).Password;
        }
    }
    Type returnType = ((System.Reflection.MethodInfo)args.Method).ReturnType;
    object returnObj = Activator.CreateInstance(returnType);
    MethodBase mb = args.Method;
    AuthenticationManager authenticate = new AuthenticationManager();
    string authenticationMessage = null;
    int? userId = authenticate.Authenticate("", "", out authenticationMessage);
    if (userId.HasValue)
    {   // User is authenticated, check authorization
        AuthorizationManager authorize = new AuthorizationManager();
        bool isAuthorized = authorize.IsAuthorized
                            (userId.Value, mb.DeclaringType.FullName, mb.Name);
        if (isAuthorized)
        {   // User is also authorized on using this services particular method
            // Set base response as OK for this authentication and authorization.
            // Note that this value will possibly be overwritten 
            // by the service method itself, so may be skipped
            BaseResponse response = new BaseResponse
            {
                IsException = false,
                IsSuccess = true,
                Messages = null
            };
            // Set execution flow to continue to method execution
            args.FlowBehavior = FlowBehavior.Continue;
            // Set the return value as BaseResponse object
            args.ReturnValue = response;
        }
        else
        {   // Authorization failed, return proper response
            // Get IsSuccess, IsException and Messages properties of the BaseRespone object
            PropertyInfo piIsSuccess = returnType.GetProperty("IsSuccess");
            PropertyInfo piIsException = returnType.GetProperty("IsException");
            PropertyInfo piMessages = returnType.GetProperty("Messages");
            // Set proper values on BaseReponse object
            piIsSuccess.SetValue(returnObj, false);,
            piIsException.SetValue(returnObj, false);
            piMessages.SetValue(returnObj, new string[] { $"You are not authorized to call 
                       this service method ({mb.DeclaringType.FullName} - {mb.Name})" });
            // Set execution flow to return
            args.FlowBehavior = FlowBehavior.Return;
            // Set the return value as BaseResponse object
            args.ReturnValue = returnObj;
        }
    }
    else
    {   // Authentication failed, return proper response
        // Get IsSuccess, IsException and Messages properties of the BaseRespone object
        PropertyInfo piIsSuccess = returnType.GetProperty("IsSuccess");
        PropertyInfo piIsException = returnType.GetProperty("IsException");
        PropertyInfo piMessages = returnType.GetProperty("Messages");
        // Set proper values on BaseReponse object
        piIsSuccess.SetValue(returnObj, false);
        piIsException.SetValue(returnObj, false);
        piMessages.SetValue(returnObj, new string[] { authenticationMessage });
        // Set execution flow to return
        args.FlowBehavior = FlowBehavior.Return;
        // Set the return value as BaseResponse object
        args.ReturnValue = returnObj;
    }
}

This was the third part of our series, explaining adding the authentication and authorization process to our service requests.

You can read the next part (Logging) here.

History

  • 28th February, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)