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.
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.
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.
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:
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:
public class AuthenticationManager
{
public int? Authenticate(string username, string password, out string message)
{
message = null;
}
}
public class AuthorizationManager
{
public bool IsAuthorized(int userId, string FullyQualifiedServiceName, string MethodName)
{
}
}
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.
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)
{
AuthorizationManager authorize = new AuthorizationManager();
bool isAuthorized = authorize.IsAuthorized
(userId.Value, mb.DeclaringType.FullName, mb.Name);
if (isAuthorized)
{
BaseResponse response = new BaseResponse
{
IsException = false,
IsSuccess = true,
Messages = null
};
args.FlowBehavior = FlowBehavior.Continue;
args.ReturnValue = response;
}
else
{
PropertyInfo piIsSuccess = returnType.GetProperty("IsSuccess");
PropertyInfo piIsException = returnType.GetProperty("IsException");
PropertyInfo piMessages = returnType.GetProperty("Messages");
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})" });
args.FlowBehavior = FlowBehavior.Return;
args.ReturnValue = returnObj;
}
}
else
{
PropertyInfo piIsSuccess = returnType.GetProperty("IsSuccess");
PropertyInfo piIsException = returnType.GetProperty("IsException");
PropertyInfo piMessages = returnType.GetProperty("Messages");
piIsSuccess.SetValue(returnObj, false);
piIsException.SetValue(returnObj, false);
piMessages.SetValue(returnObj, new string[] { authenticationMessage });
args.FlowBehavior = FlowBehavior.Return;
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