In the last few post we have been trying to set the stage to start discussing about creating Identity aware application architecture using Windows Identity Foundation [WIF]. We have been going through these posts to ease ourselves into the technology. This post is also to set one step towards that direction. This is to discuss role based security outside the Identity and Claims world. Please remember that this is the most common way we have been implementing security authentication and authorization in our services specially inside the firewall in an intranet organization setup.
Let's consider our organization as a financial type of organization where we have different applications and services to help the employees carrying out their activities. One such users are traders. They execute trades which are then forwarded to any specific exchange for placement. We need to make sure that only traders are allowed to execute trade. One such way is to check at the service level if the requester is a trader and allow the operation only under such case.
In WCF scenarios for a Windows Active Directory based domains, users belong to different domain groups. These groups are used by applications to determine the user privileges before executing the actual operation. The same approach has been traditionally taken to implement security for our services. This is exactly what we would be trying to discuss in this post. For our organization, let us assume that all traders belong to Traders group.
Let's start by creating a WCF Service Library project, TradeExecutionServiceLib.
Now let's update the name of the service contract and the implementation to ITradeExecutor and TradeExecutor respectively. Doing it on the solution explorer level would ensure that the class names are updated inside their respective files. This should also update the service configuration in App.Config file.
Now let us update ITradeExecutor as follows:
Here we have also included a service operation, ExecuteTrade(). This is certainly very simple way on the service side for looking at the trades execution but this should be enough to understand the concept of role based security authorization in WCF, which is the purpose of today's post. This requires the instrument's symbol and quantity to be passed as input parameters. Now we update the service implementation as follows:
The implementation is just writing the details about the symbol and traded quantity on the console. Now let us add authorization code. We need to make sure that only traders belonging to Traders group have access to ExecuteTrade() operation. In WCF and code access security scenarios, this can easily be implemented using PrincipalPermission attribute from System.Security.Permissions namespace in mscorlib assembly. We can also directly use PrincipalPermssion class in a non-declarative way.
Decorating Service Operations with PrincipalPermissionAttribute
One way to authorize user's role is to decorate the operation with PrincipalPermissionAttribute. Here Role would mean the Windows group we are specifically looking for. Now this becomes the responsibility of runtime hosting the service to ensure that the user of the service is a member of the specified group and deny access otherwise.
Now we need to test if the authorization logic is actually working. Since this is a very simple service, we don't need to build a special client just to test the service. Here we can simply use WCF Test Client installed using the framework. We don't even need to host this as Visual Studio would automatically host it to the default WCF Service Host. run this with the default service configuration. Just make sure that you have the service project set as start up project in the solution. Since we have only one project in the solution, this is the default case here. You should see the following toast appear in the system tray when you run the service.
If you double click the icon on the tray, you should see that service is hosted and the status should show as Started.
This should also launch WCF Test Client with the operations listed on the service.
Let us provide the input arguments for ExecuteTrade operation and hit Invoke. Since were debugging it, we should be able to see the following security exception in the visual studio editor:
If you continue the execution then you should see the following on the WCF Test Client side which just shows the client that there is something wrong with the request. When we write our custom clients we should be handling this case more gracefully showing the user the exact cause of the error or how to request permissions if he is a legitimate user of the service.
Since the service operation requires the user to belong to a certain role, it blows up as there is no such membership for the requesting user. Not even such a group exists on the machine [CHEETA]. To keep it simple, let us create a Windows Group using the Computer Management tool as follows:
Let's add the user (requester) to the Traders group now. See the highlighted information requiring the user to login again in order for the changes to take effect.
Now let us update ServiceModel section of the service configuration in App.Config to use Windows security. Here we have updated the binding to net TCP. We have also updated authorization behavior use Windows Accounts.
In order to test that the logic is actually working let us debug the code. Let us insert a break point on the ExecuteTrade operation side and run the service. As described previously the service is hosted and WCF Test Client is launched, we provide the same input arguments as before ("MSFT" and 100) and invoke service operation. since the user has such membership now, the security authorization logic calls the method and hits the break point.
The class hierarchy of PrinicipalPermissionAttribute is as follows:
Non-Declarative Permission Demands
In the above example we provided the required membership requirements by adding PrinicipalPermissionAttribute to the operation contract implementation. The same behavior can be achieved by doing the same thing in the code. The unauthorized access would be determined by WCF Service pipeline and an exception would be generated which would result in a fault for the user. In the below code, we are just updating the code to demand membership using the same Principal Permission, but using code.
The definition of PrincipalPermission is as follows:
If we look at the definition of PrincipalPermissionAttribute, it is using the same IPermission.