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.
01.
public
class
MyAuthorizedController {
02.
protected
override
void
OnActionExecuting(ActionExecutingContext filterContext)
03.
{
04.
string
targetAction = filterContext.ActionDescriptor.ActionName;
05.
string
idParameter = filterContext.ActionParameters[
"id"
];
06.
string
username = filterContext.HttpContext.User.Identity.Name;
07.
bool
isAllowed = IAuthorizationManager.Validate(username, idParameter , targetAction);
08.
if
(isAllowed)
09.
base
.OnActionExecuting(filterContext);
10.
throw
new
UnauthorizedAccessException();
11.
}
12.
}
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.
01.
public
class
MyAuthorizationManager : System.ServiceModel.ServiceAuthorizationManager
02.
{
03.
protected
override
bool
CheckAccessCore(OperationContext operationContext)
04.
{
05.
// If using ASP.NET authentication, we can retrieve the user name from HttpContext
06.
// Of course this won't work for anonymous requests, you;d need to handle those as well
07.
var userName = HttpContext.Current.User.Identity;
08.
09.
// the target service is the actual service class being called.
10.
var targetService = operationContext.Host.Description.ServiceType;
11.
12.
// not used in the specfic authorization manager, but could be useful depending on your scenario
13.
var contractDescription = getOperationContractType(operationContext);
14.
15.
// The following key will help check whether or not this was a valid REST request
16.
// If the key is found, we'll retrieve the UriTemplateMatch results which will give us info about operation name and
17.
string
key = WebHttpDispatchOperationSelector.HttpOperationSelectorUriMatchedPropertyName;
18.
var props = operationContext.IncomingMessageProperties;
19.
UriTemplateMatch match =
null
;
20.
if
(props.ContainsKey(key) && (
bool
)props[key])
21.
match = operationContext.IncomingMessageProperties[
"UriTemplateMatchResults"
]
as
UriTemplateMatch;
22.
else
23.
// in this case, we'll ignore all non-REST requests
24.
return
true
;
25.
26.
// now that we've found the UriTemplateMatch instance, we can find information about our method parameters
27.
// we'll use 'id' as an example
28.
if
(match.BoundVariables.AllKeys.Contains(
"id"
, StringComparer.InvariantCultureIgnoreCase))
29.
idValue = match.BoundVariables[
"id"
];
30.
}
31.
32.
33.
// For information about the data contract (the interface mapped to the endpoint being used),
34.
// you'll need to loop through all EndPoints in operationContext.Host.Description.Endpoints and look for
35.
// one that has the same namespace and contract name as your context's EndpointDispatcher
36.
private
static
Dictionary<
string
,=
""
contractdescription=
""
> contractMap =
new
Dictionary<
string
,=
""
contractdescription=
""
>();
37.
private
static
ContractDescription getOperationContractType(OperationContext operationContext)
38.
{
39.
if
(contractMap.Count == 0){
40.
lock
(contractMap){
41.
ServiceEndpointCollection endpoints = operationContext.Host.Description.Endpoints;
42.
foreach
(ServiceEndpoint endpoint
in
endpoints) {
43.
string
contractKey = endpoint.Contract.Namespace + endpoint.Contract.Name;
44.
contractMap[contractKey] = endpoint.Contract;
45.
}
46.
}
47.
}
48.
EndpointDispatcher dispatcher = operationContext.EndpointDispatcher;
49.
string
key = dispatcher.ContractNamespace + dispatcher.ContractName;
50.
return
contractMap[key];
51.
}
52.
}</
string
></
string
>
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:
01.
protected
override
bool
CheckAccessCore(OperationContext operationContext)
02.
{
03.
[...]
04.
string
operationName = getOperationName(match.Data);
05.
bool
isAuthorized = IAuthorizationManager.Validate(username, id, operationName);
06.
}
07.
08.
private
static
object
operationNameLock =
new
Object();
09.
private
static
PropertyInfo operationNamePropertyInfo;
10.
private
static
string
getOperationName(
object
data){
11.
if
(operationNamePropertyInfo ==
null
){
12.
lock
(operationNameLock){
13.
operationNamePropertyInfo = data.GetType().GetProperty(
"OperationName"
);
14.
}
15.
}
16.
try
{
17.
return
operationNamePropertyInfo ==
null
?
null
: operationNamePropertyInfo.GetValue(data,
null
)
as
string
;
18.
}
catch
{
19.
return
null
;
20.
}
21.
}