Tuesday, April 28, 2009

WCF REST & ASP.NET MVC authorization

Last week I needed to implement an authorization scheme in our MVC and WCF apps. I found a bunch of resources on how to implement Role or Claims-based authorization in both frameworks, but they all required adding CLR attributes on controller actions and service operations - a bit of messy for my taste, and required hard-coding your authorization rules, which didn’t fit my requirements. We developed a system that allow us to define authorization rules at run-time, and I was looking for a way to write a single authorization front controller for all service / MVC calls. I could have gone the HTTP module way, but that would have mean writing code to parse the request & figure out what controller, contract, operation are being called with what parameters. Since all of this is already done for in the MVC and WCF frameworks, I wanted to have this authorization controller called after the request is parsed and before the operation is executed.

public class MyAuthorizedController {
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
string targetAction = filterContext.ActionDescriptor.ActionName;
string idParameter = filterContext.ActionParameters["id"];
string username = filterContext.HttpContext.User.Identity.Name;
bool isAllowed = IAuthorizationManager.Validate(username, idParameter , targetAction);
if(isAllowed)
base.OnActionExecuting(filterContext);
throw new UnauthorizedAccessException();
}
}


Now just make sure all your secured controllers extend this class, and you’re done.



WCF looked just as easy at first. In the ServiceBehavior configuration, you can define a custom authorization class in the serviceAuthorization’s ServiceAuthorizationManagerType property. The custom class needs to extend ServiceAuthorizationManager, which exposes a protected virtual method called “CheckAccessCore”. Just override that method.



public class MyAuthorizationManager : System.ServiceModel.ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
// If using ASP.NET authentication, we can retrieve the user name from HttpContext
// Of course this won't work for anonymous requests, you;d need to handle those as well
var userName = HttpContext.Current.User.Identity;

// the target service is the actual service class being called.
var targetService = operationContext.Host.Description.ServiceType;

// not used in the specfic authorization manager, but could be useful depending on your scenario
var contractDescription = getOperationContractType(operationContext);

// The following key will help check whether or not this was a valid REST request
// If the key is found, we'll retrieve the UriTemplateMatch results which will give us info about operation name and
string key = WebHttpDispatchOperationSelector.HttpOperationSelectorUriMatchedPropertyName;
var props = operationContext.IncomingMessageProperties;
UriTemplateMatch match = null;
if (props.ContainsKey(key) && (bool)props[key])
match = operationContext.IncomingMessageProperties["UriTemplateMatchResults"] as UriTemplateMatch;
else
// in this case, we'll ignore all non-REST requests
return true;

// now that we've found the UriTemplateMatch instance, we can find information about our method parameters
// we'll use 'id' as an example
if(match.BoundVariables.AllKeys.Contains("id", StringComparer.InvariantCultureIgnoreCase))
idValue = match.BoundVariables["id"];
}


// For information about the data contract (the interface mapped to the endpoint being used),
// you'll need to loop through all EndPoints in operationContext.Host.Description.Endpoints and look for
// one that has the same namespace and contract name as your context's EndpointDispatcher
private static Dictionary contractMap = new Dictionary();
private static ContractDescription getOperationContractType(OperationContext operationContext)
{
if(contractMap.Count == 0){
lock(contractMap){
ServiceEndpointCollection endpoints = operationContext.Host.Description.Endpoints;
foreach (ServiceEndpoint endpoint in endpoints) {
string contractKey = endpoint.Contract.Namespace + endpoint.Contract.Name;
contractMap[contractKey] = endpoint.Contract;
}
}
}
EndpointDispatcher dispatcher = operationContext.EndpointDispatcher;
string key = dispatcher.ContractNamespace + dispatcher.ContractName;
return contractMap[key];
}
}


So now we’ve found out the current user's username, the service’s type, the value of our id parameter, and the datacontract’s description. We’re getting close but – where’s the actual operation name (the name of the method being called)??



When using SOAP, you can easily find the action being called by using operationContext.IncomingMessageHeaders.Action. The action looks like this: http://MyService/IMyContract/MyAction1. This would be easy to parse.



Unfortunately, when using REST EndPoints, the Action message header is blank. So where is it the information stored? In an obscure untyped “data” property found in the UriTemplateMatch. When debugging, you’ll see that this property is actually an instance of type WebHttpDispatchOperationSelector+WCFLookupResult, which is a private class. It exposes 2 properties – Method (HTTP method) and OperationName (the method called on the service, which is what we’re looking for).



Why the WCF team would decide to hide the OperationName is beyond me. I kept thinking I was missing something, I went through every single property of the OperationContext class (a never-ending project), and I just couldn’t find it anywhere. How are you supposed to authorize a service call if you don’t know what action is called on the service??



My current workaround is to use reflection. Here’s the method:



	protected override bool CheckAccessCore(OperationContext operationContext)
{
[...]
string operationName = getOperationName(match.Data);
bool isAuthorized = IAuthorizationManager.Validate(username, id, operationName);
}

private static object operationNameLock = new Object();
private static PropertyInfo operationNamePropertyInfo;
private static string getOperationName(object data){
if(operationNamePropertyInfo == null){
lock(operationNameLock){
operationNamePropertyInfo = data.GetType().GetProperty("OperationName");
}
}
try{
return operationNamePropertyInfo == null ? null : operationNamePropertyInfo.GetValue(data, null) as string;
}catch{
return null;
}
}

WCF, REST & POX – a few notes about serialization

I usually use 2 types of endpoints when using WCF: SOAP for server - server communication, and REST/JSON for browser – server service calls. But yesterday, while trying to debug some POST & PUT HTTP calls in Fiddler, I decided to use a REST/XML endpoint. After all, POX is a bit easier on the eyes than JSON, and this would give me an opportunity to test my XML endpoints, which I had created but never used.

Of course all I got were HTTP 400 responses – Method not allowed. The call would fail before ever reaching the service, and therefore was probably related to some DataContractSerializer exception.

My first thought was to use the .Net source code and inspect was going on inside WCF. I had been using the “Enable .Net Source Stepping” feature of VS 2008 for a while, and it had helped immensely for various System.Web & System.Net issues. Unfortunately, I found out WCF libraries (System.ServiceModel etc…) are not included. I sent a quick not to Scott Guthrie (who posted a year ago that those libraries would be coming soon) asking about for an ETA, and he was kind enough to forward my request to Bob Dimpsey, but I haven’t heard back yet  (EDIT: Bob did get back to me last night, it looks like this will be coming out soon, but no ETA yet). So for now, no WCF source code.

Next I found some resources on how to configure WCF tracing. I configured the diagnostics to trace to the main output output window and finally figured out what was going on.

It turns out the DataContractSerializer was requiring a namespace in my message’s XML contents. Duh. I had forgotten to define the xml namespace in the DataContract. I fixed the problem, changed the message to added the xmlns attribute to the root of my xml data, and things started working.

Well, sort of. Some of my properties were missing in the deserialized object. After looking a bit deeper, it turns out DataContractSerializer forces specific ordering of your XML elements. When adding the DataMember attribute, there’s a way to specify an “Order” property. If that property is not defined, DataContractSerializer defaults to alphabetized names. This means if your class has 2 properties Name, FirstName defined, your XML data will need to list <FirstName /> <LastName /> – ordered alphabetically. Apparently it has something to do with performance and versioning.

Once I made sure my elements were alphabetized, I was able to text my REST services using XML without any problem.