Traditionally, to implement role based authorization in .NET you would use PrincipalPermission attributes, e.g.:
[PrincipalPermission(SecurityAction.Demand, Name = "YourDomain\\User1", Role = "YourDomain\\Role1")]
public void MethodX(...)
{
. . .
}
In my opinion, there are two problems with this approach:
1. At development time you need to make a decision on which roles are allowed/denied
2. In case of WCF services (at least on .NET 3.5), there doesn’t appear to be a way to add the PrincipalPermission attribute at a class level and you end up adding this attribute to every method (it appears that, at the construction time, currentPrincipal.Identity.IsAuthenticated is returning false, so no IsInRole checks are performed).
Here is how I address this requirement:
1. In WCF services config file (e.g. web.config, if you’re using IIS as a host), add the following configuration:
<configuration>
<configSections>
<section name="authorizationRules" type=" WCFExtensions.AuthorizationRulesSection, WCFExtensions" />
</configSections>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="YourBehaviorNameHere">
<serviceAuthorization serviceAuthorizationManagerType="WCFExtensions.AuthorizationManager, WCFExtensions" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
<authorizationRules>
<services>
<service name="Service1.svc" defaultAccess="deny" overrideUsers=" YourDomain\Me " overrideRoles="YourDomain\Role1,YourDomain\Role2">
<operation name="IService1/Method1" allowUsers="YourDomain\User2" allowRoles="" denyUsers="" denyRoles="" />
</service>
</services>
</authorizationRules>
</configuration>
The idea is simple – at a service level, you can allow or deny access as a default behavior, and override that rule by denying/allowing only certain users and/or roles. This authorization rule will apply to all methods (operations) unless you override that behavior by adding <operation> element.
Here is an example of what I mean… Let’s say you want to allow access to Service1 only to one group – MyAppUsers, but, for a very sensitive Delete operation, you only want MyAppAdmins to be able to perform it. To implement this requirement, you’d configure it as follows:
<service name="Service1.svc" defaultAccess="deny" overrideRoles="MyAppUsers">
<operation name="IService1/Delete" denyRoles="MyAppUsers" allowRoles="MyAppAdmins" />
2. To implement the authorization rules at run time, create a class library WCFExtensions and add the two classes – AuthorizationManager.cs and ConfigurationEx.cs with the implementation listed below:
using System;
using System.Security.Principal;
using System.ServiceModel;
namespace WCFExtensions
// TODO: add SEH and logging
public class AuthorizationManager : ServiceAuthorizationManager
public override bool CheckAccess(OperationContext operationContext, ref System.ServiceModel.Channels.Message message)
bool result = false;
// Allow all calls to the mex endpoint
if ("IMetadataExchange" == operationContext.EndpointDispatcher.ContractName)
result = true;
else
// We require Windows authentication
if (operationContext.ServiceSecurityContext.WindowsIdentity != null)
// Caller identity
WindowsIdentity caller = operationContext.ServiceSecurityContext.WindowsIdentity;
// Get service and operation
Uri uri = operationContext.IncomingMessageHeaders.To;
string service = uri.PathAndQuery;
string operation = new Uri(operationContext.IncomingMessageHeaders.Action).AbsolutePath.Substring(1);
object o = System.Configuration.ConfigurationManager.GetSection("authorizationRules");
if (o == null)
// If the section doesn't exist, let everybody in...
else if (o is WCFExtensions.AuthorizationRulesSection)
result = WCFExtensions.AuthorizationRulesSection.CheckAccess(service, operation, caller);
throw new Exception("authorizationRules section in configuration file is of the wrong type");
return result;
using System.Collections.Generic;
using System.Configuration;
public enum DefaultAccessMode
allow,
deny
public class AuthorizationRulesSection : ConfigurationSection
private static readonly AuthorizationRulesSection Instance = (AuthorizationRulesSection)System.Configuration.ConfigurationManager.GetSection("authorizationRules");
private readonly ConfigurationProperty _services =
new ConfigurationProperty("services", typeof(ServiceElementCollection));
[ConfigurationProperty("services", IsDefaultCollection = true)]
public ServiceElementCollection Services
get
return (ServiceElementCollection)base[_services];
public static bool CheckAccess(string serviceName, string operationName, WindowsIdentity caller)
// Find the service
ServiceElementCollection services = Instance.Services;
// Try to match taking into account the number of Uri fragments in the config file
ServiceElement serviceElem = null;
foreach (ServiceElement item in services)
if (serviceName.EndsWith(item.Name))
serviceElem = item;
break;
if (serviceElem != null)
AuthRules rules = new AuthRules(serviceElem.DefaultAccess, serviceElem.OverrideUsers, serviceElem.OverrideRoles);
OperationElement opElem = serviceElem.Operations[operationName];
if (opElem != null)
rules.AddRule(opElem.AllowUsers, opElem.AllowRoles, opElem.DenyUsers, opElem.DenyRoles);
bool? evalResult = rules.CheckAccess(caller);
if (evalResult == null)
// No rule => use defaults
result = (serviceElem.DefaultAccess == DefaultAccessMode.allow);
result = (bool)evalResult;
// If there is no restriction, allow access
[ConfigurationCollection(typeof(ServiceElement), AddItemName="service")]
public class ServiceElementCollection : ConfigurationElementCollection
public ServiceElementCollection()
: base(StringComparer.OrdinalIgnoreCase)
protected override ConfigurationElement CreateNewElement()
return new ServiceElement();
protected override Object GetElementKey(ConfigurationElement element)
return ((ServiceElement)element).Name;
public new ServiceElement this[string name]
// Force the get by key, not index
object key = name;
return (ServiceElement)base.BaseGet(key);
public ServiceElement this[int index]
return (ServiceElement)base.BaseGet(index);
public class ServiceElement : ConfigurationElementCollection
private readonly ConfigurationProperty _name = new ConfigurationProperty("name", typeof(string), String.Empty, null, null, ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey);
private readonly ConfigurationProperty _defaultAccess = new ConfigurationProperty("defaultAccess", typeof(DefaultAccessMode));
private readonly ConfigurationProperty _overrideUsers = new ConfigurationProperty("overrideUsers", typeof(string));
private readonly ConfigurationProperty _overrideRoles = new ConfigurationProperty("overrideRoles", typeof(string));
private readonly ConfigurationProperty _operations = new ConfigurationProperty(null, typeof(OperationElementCollection), null, ConfigurationPropertyOptions.IsDefaultCollection);
[ConfigurationProperty("name", Options = ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey, DefaultValue = "")]
public string Name
get { return (string)base[_name]; }
set { base[_name] = value; }
[ConfigurationProperty("defaultAccess", DefaultValue = DefaultAccessMode.allow)]
public DefaultAccessMode DefaultAccess
get { return (DefaultAccessMode)base[_defaultAccess]; }
set { base[_defaultAccess] = value; }
[ConfigurationProperty("overrideUsers", DefaultValue = "")]
public string OverrideUsers
get { return (string)base[_overrideUsers]; }
set { base[_overrideUsers] = value; }
[ConfigurationProperty("overrideRoles", DefaultValue = "")]
public string OverrideRoles
get { return (string)base[_overrideRoles]; }
set { base[_overrideRoles] = value; }
[ConfigurationProperty("", IsDefaultCollection = true)]
public OperationElementCollection Operations
return (OperationElementCollection)base[_operations];
return new OperationElement();
return ((OperationElement)element).Name;
[ConfigurationCollection(typeof(OperationElement), AddItemName = "operation")]
public class OperationElementCollection : ConfigurationElementCollection
public OperationElementCollection()
public new OperationElement this[string name]
return (OperationElement)base.BaseGet(key);
public OperationElement this[int index]
return (OperationElement)base.BaseGet(index);
public class OperationElement : ConfigurationElement
private readonly ConfigurationProperty _allowUsers = new ConfigurationProperty("allowUsers", typeof(string));
private readonly ConfigurationProperty _allowRoles = new ConfigurationProperty("allowRoles", typeof(string));
private readonly ConfigurationProperty _denyUsers = new ConfigurationProperty("denyUsers", typeof(string));
private readonly ConfigurationProperty _denyRoles = new ConfigurationProperty("denyRoles", typeof(string));
[ConfigurationProperty("allowUsers", DefaultValue = "")]
public string AllowUsers
get { return (string)base[_allowUsers]; }
set { base[_allowUsers] = value; }
[ConfigurationProperty("allowRoles", DefaultValue = "")]
public string AllowRoles
get { return (string)base[_allowRoles]; }
set { base[_allowRoles] = value; }
[ConfigurationProperty("denyUsers", DefaultValue = "")]
public string DenyUsers
get { return (string)base[_denyUsers]; }
set { base[_denyUsers] = value; }
[ConfigurationProperty("denyRoles", DefaultValue = "")]
public string DenyRoles
get { return (string)base[_denyRoles]; }
set { base[_denyRoles] = value; }
internal class AuthRules
private List<string>
_allowUsers = new List<string>(),
_denyUsers = new List<string>(),
_allowRoles = new List<string>(),
_denyRoles = new List<string>();
internal AuthRules(DefaultAccessMode mode, string overrideUsers, string overrideRoles)
if (mode == DefaultAccessMode.allow)
_denyUsers.AddRange(overrideUsers.ToLower().Split(','));
_denyRoles.AddRange(overrideRoles.ToLower().Split(','));
_allowUsers.AddRange(overrideUsers.ToLower().Split(','));
_allowRoles.AddRange(overrideRoles.ToLower().Split(','));
internal void AddRule(string allowUsers, string allowRoles, string denyUsers, string denyRoles)
AddRule(allowUsers, _allowUsers, _denyUsers);
AddRule(allowRoles, _allowRoles, _denyRoles);
AddRule(denyUsers, _denyUsers, _allowUsers);
AddRule(denyRoles, _denyRoles, _allowRoles);
private void AddRule(string rule, List<string> addToList, List<string> removeFromList)
if (string.IsNullOrEmpty(rule) == false)
foreach (string item in rule.ToLower().Split(','))
addToList.Add(item);
if (removeFromList.Contains(item))
removeFromList.Remove(item);
internal bool? CheckAccess(WindowsIdentity caller)
bool? result = null; // undetermined
// Users take precedence over roles; deny takes precedence over allow
string userName = caller.Name.ToLower();
if (_denyUsers.Contains(userName))
result = false;
else if (_allowUsers.Contains(userName))
// When it comes to groups, if user belongs to one of the allowed groups, access is granted
WindowsPrincipal callerPrincipal = new WindowsPrincipal(caller);
foreach (string group in _allowRoles)
if (callerPrincipal.IsInRole(group))
// Not in the allowedRoles => look at denied roles
if (result == null)
// Make sure the caller is not in one of the denied groups
foreach (string group in _denyRoles)
3. Compile and deploy WCFExtensions library to the bin folder of your WCF Services
As always, feel free to use/modify the code for your project’s needs… but if you do, I’d appreciate if you leave your comments/feedback…