WCF Custom Authentication and Impersonation
During the last days I needed to develop a custom Authentication and impersonation mechanism for Indigo. It was not so easy, due to the lack of documentation, since we're still in early betas/ctps. By the way I handled it and here is what I've discoverded, in case someone else is looking for this.
Of course, because I'm just a WCF lover and I'm working with it because I need to use it in a real project of a customer of mine, please if someone (eventually from Indigo team...) has any kind of comment or errata corrige about this post ... please do it, I will really apreciate it. Thanks.
WCF support many different authentication techniques and here you can see a sample WCF host configuration file:
<?
xml version="1.0" encoding="utf-8" ?><
configuration> <system.serviceModel><services>
<service type="DevLeap.Indigo.MyService, DevLeap.Indigo" behaviorConfiguration="MyServiceBehavior">
<endpoint address="net.tcp://localhost:35000/Services/MyService/" binding="netTcpBinding"
bindingConfiguration="MyServiceBinding" contract="DevLeap.Indigo.IMyService, DevLeap.Indigo" />
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="MyServiceBinding">
<security mode="Message">
<message clientCredentialType="UserName" defaultProtectionLevel="EncryptAndSign" />
</security>
</binding>
</netTcpBinding>
</bindings>
<behaviors>
<behavior name="MyServiceBehavior" returnUnknownExceptionsAsFaults="True">
<metadataPublishing enableGetWsdl="true" />
<serviceAuthorization principalPermissionMode="Custom" />
<!-- UseAspNetRoles -->
<serviceCredentials>
<serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
<userNamePassword membershipProviderName="MyMembershipProvider" />
</serviceCredentials>
</behavior>
</behaviors>
</system.serviceModel>
<
system.web><membership>
<providers>
<add name="MyMembershipProvider" type="DevLeap.WSSecurity.MyMembershipProvider, DevLeap.WSSecurity" />
</providers>
</membership>
</system.web>
</
configuration>Pay attention to the message section inside the netTcpBinding configuration named MyServiceBinding. Here I declared that I'm going to use "UserName" clientCredentialType. It means that the consumer needs to provide a UsernameToken to the service, in order to be authenticated.
Later in the config file, inside the behaviors section, I declared a custom behavior named MyServiceBehavior where I defined the way I'd like to handle the UsernameToken, in order to get an IPrincipal for the user, configuring the principalPermissionMode attribute of the
- None: it's clear.
- UseWindowsGroups: uses Windows users database and WindowsPrincipal and WindowsIdentity
- UseAspNetRoles: refers to ASP.NET 2.0 MembershipProvider, with its IIdentity and IPrincipal implementations.
- Custom: a custom mechanism (the one we're going to use).
The configuration file above declarese a Custom mechanism, but there's also the configuration for using a MembershipProvider, just in case you'd like it (see userNamePassword element inside the behavior configuration).
If you define a Custom principalPermissionMode you have to define and configure a custom ServiceAuthorizationBehavior. Follows a sample to do that by code:
using (ServiceHost host = new ServiceHost(typeof(MyService))){
ServiceAuthorizationBehavior sa = host.Description.Behaviors.Find<ServiceAuthorizationBehavior>();
sa.PrincipalPermissionMode = PrincipalPermissionMode.Custom;
sa.AuthorizationDomain = new AuthorizationDomain(new IAuthorizationPolicy[] { new DevLeap.WSSecurity.DevLeapAuthorizationPolicy() });
ServiceCredentials original = host.Description.Behaviors.Remove<ServiceCredentials>();
host.Description.Behaviors.Add(new DevLeap.WSSecurity.DevLeapServiceCredentials(original, null));
host.Open(); Console.WriteLine("Host listening ...");
Console.ReadLine();
}
Firts of all I get a reference to the ServiceAuthorizationBehavior for my host, then I declare a custom AuthorizationDomain. An AuthorizationDomain is based on a set of IAuthorizationPolicy implementations, defined inside the System.Security.Authorization of .NET 2.0. An IAuthorizationPolicy is a base interface to implement in .NET if you want to define custom Claims. For instance my DevLeapAuthorizationPolicy defines that I'd like to map my users with a custom IPrincipal, built with their credentials. Here is the code:
using
System;using System.Collections.Generic;
using System.Text;
using System.Security.Authorization;
using System.Security.Principal;
using DevLeap.Security;
namespace
DevLeap.WSSecurity{ public class DevLeapAuthorizationPolicy : IAuthorizationPolicy
{ string id = Guid.NewGuid().ToString(); public string Id
{
get { return this.id; }
} public ClaimSet Issuer
{
get { return ClaimSet.Anonymous; }
} public bool Evaluate(EvaluationContext context, ref object state)
{
object primaryIdentity;
if (!context.Properties.TryGetValue("PrimaryIdentity", out primaryIdentity)) return false;
context.Properties["Principal"] = new DevLeapPrincipal(new DevLeapIdentity(((IIdentity)primaryIdentity).Name));
return true;
}
}
}
The interesting part of my custom IAuthorizationPolicy is the Evaluate method implementation. Here, using the context of the claim evaluation, I try to get a property called PrimaryIndentity. This property represents the identity discovered by WCF during user credentials verification. If I'm missing the PrimaryIdentity it means that the user has not been identified, so my claim evaluation fails. Otherwise I convert the PrimaryIdentity, that is an IIdentity implementation (it's a GenericIdentity), into my custom DevLeapIdentity and I define a custom DevLeapPrincipal, based on DevLeapIdentity, that I save inside the context Principal property. WCF will extract by itself the Principal property from the context, when calling my service implementation, impersonating it during the execution of my operations.
In fact if I try to check the IPrincipal and the IIdentity associated with the current thread, inside an operation execution, I'll get back my DevLeapPrincipal and DevLeapIdentity:
Console.WriteLine(System.Threading.Thread.CurrentPrincipal.GetType().Name);Console.WriteLine(System.Threading.Thread.CurrentPrincipal.Identity.GetType().Name);
Console.WriteLine(System.Threading.Thread.CurrentPrincipal.Identity.Name);
Console.WriteLine(OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.GetType().Name);
Console.WriteLine(OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name);
Here is the output inside the Console windows, if I'm logging in with "PaoloPia" username:
DevLeapPrincipal
DevLeapIdentity
PaoloPia
GenericIdentity
PaoloPia
The last part of code that we need to define is the one that declares how to authenticate the UserNameToken in order to have a PrimaryIdentity that matches with the user credentials. A few lines above, during ServiceHost configuration, I declared also:
ServiceCredentials original = host.Description.Behaviors.Remove<ServiceCredentials>();
host.Description.Behaviors.Add(new DevLeap.WSSecurity.DevLeapServiceCredentials(original, null));
This code simply gets a reference to the ServiceCredentials configuration of my custom behavior and adds a custom implementation of a ServiceCredentials class. The default ServiceCredentials class of System.ServiceModel derives from System.ServiceModel.Security.SecurityCredentialsManager and defines ClientCertificate, ServiceCertificate, UserNamePassword and Windows authentication mechanisms. ServiceCredentials, inside its CreateTokenAuthenticator method, decides which SecurityTokenAuthenticator to use, in order to validate the token provided by the service consumer. Every SecurityTokenAuthenticator is just a class derived from SecurityTokenAuthenticator that verifies a token. WCF today by default defines two different kind of UserNameSecurityTokenAuthenticator implementations:
- WindowsUsernNamePasswordTokenAuthenticator: uses Windows database users to validate the token
- MembershipUserNamePasswordTokenAuthenticator: uses ASP.NET 2.0 MembershipProvider API to validate the token
Here is my custom ServiceCredentials implementation, in order to use my custom users database to validate the token provided by the consumer:
using
System;using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
namespace
DevLeap.WSSecurity{ public class DevLeapServiceCredentials: ServiceCredentials
{
public DevLeapServiceCredentials(ServiceCredentials original, params string[] trustedSecurityTokenServices)
{
Type scType = typeof(ServiceCredentials);
scType.GetField("userName", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(this, original.UserNamePassword);
scType.GetField("clientCertificate", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(this, original.ClientCertificate);
scType.GetField("serviceCertificate", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(this, original.ServiceCertificate);
scType.GetField("windows", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(this, original.Windows);
} protected override SecurityTokenAuthenticator CreateTokenAuthenticator(SecurityTokenParameters parameters)
{
return (new DevLeapUserNamePasswordTokenAuthenticator());
}
}
}
Inside my constructor I copy to myself, via System.Reflection, the configuration of the original ServiceCredentials implementation, then I override the CreateTokenAuthenticator in order to return a custom one. Here is it:
using
System;using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using System.Security.Authorization;
using System.Collections.ObjectModel;
using DevLeap.Security;
namespace
DevLeap.WSSecurity{ public class DevLeapUserNamePasswordTokenAuthenticator: UserNameSecurityTokenAuthenticator
{ private class DevLeapUserNamePasswordToken: UserNameSecurityToken
{
public DevLeapUserNamePasswordToken(string userName, string password): this(userName, password, SecurityToken.GenerateId())
{ } public DevLeapUserNamePasswordToken(string userName, string password, string id): base(userName, password, id)
{ } protected override void ValidateCore(SecurityTokenResolver tokenResolver)
{
if (DevLeapSecurity.AuthenticationProvider.ValidateUser(base.UserName, base.Password) <= 0)
{
throw new SecurityTokenValidationException("UserNamePasswordTokenAuthenticationFailed");
}
}
}
public override SecurityToken CreateUserNamePasswordToken(string id, string userName, string password){
UserNameSecurityToken token = null;
if (id == null)
token = new DevLeapUserNamePasswordTokenAuthenticator.DevLeapUserNamePasswordToken(userName, password);
else
token = new DevLeapUserNamePasswordTokenAuthenticator.DevLeapUserNamePasswordToken(userName, password, id);
token.Validate();
return token;
}
}
}
Inside the CreateUserNamePasswordToken I define an instance of a custom UserNameToken of my own (DevLeapUserNamePasswordToken) that overrides the ValidateCore method, calling a custom Authorization mechanism of my own, defined in my application Security infrastructure.
The keypoint of this custom authentication tecnique is that now I'm able to call my business layer, from my operations code, using imperative and declarative authorization inside the code of my business layer, even if my consumer is calling me through WCF and not directly, and it works with many different protocol bindings!
That's all! Hope this is usefull for someone else ...