Paolo Pialorsi - Bridge The Gap!

Living in a Service Oriented World

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
serviceAuthorization element. Possible values - also suggested by intellisense - are:

  • 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 ...

Posted: Dec 08 2005, 11:29 AM by paolopia | with 21 comment(s)
Filed under:

Comments

David said:

Thanks for this, very helpful.

Regards,

David

# April 21, 2008 12:28 AM

Mike said:

I have an endpoint with binding set to wsHttpBinding and security set to Message with Windows client credential type. I'm trying to use a token authenticator with the endpoint but can't seem to configure it properly.

Should WindowsSecurityTokenAuthenticator authenticator be used here and how (can't find examples anywhere)? If not, how can I implement a custom token authenticator for the endpoint?

I appreciate any help you can provide!

# February 5, 2010 11:00 AM

roy said:

u hav used custom validation for netTcpBinding. Can same custom validation be used in basicHttpBinding

# June 23, 2010 3:03 AM

Vadim Chekan said:

I think you should return "false" from Evaluate, because you do not know, in which order authorization policies will be evaluated. It could happen that your policy is called before the one which sets "Identities", so you will miss it.

By returning 'false" you tell WCF that you want a re-evaluation if any claims were added by other policies.

Unfortunately WCF won't re-evaluate policies if some of them changed Property< so this code is still matter of luck.

# June 24, 2010 8:16 PM

DeWet said:

Hi Paolo,

Can you please post a basic example of your AuthorizationDomain mentioned above ?

Thanks

# July 20, 2010 7:21 AM

!! said:

your post subject  

"WCF Custom Authorization !!"

# October 7, 2010 8:04 AM

Oreilly said:

Hi, have you ever before thought about to write about Nintendo

or PS handheld

# August 2, 2012 3:44 PM

Soper said:

Hello! I just wanted to ask if you ever have any

other issues with hackers? My last blog (wordpress) was hacked and I ended up losing a few weeks of tough work due

to no back up. Do you have any other techniques to

stop hackers?

# December 12, 2012 2:33 PM

Solomon said:

You completed a couple of excellent points there.

Used to do a search for that the issue and found nearly all people will go in addition to together with your blog.

# December 29, 2012 9:40 AM

Huey said:

I likewise conceive so , perfectly written post! .

# December 29, 2012 11:21 PM

Cathey said:

Good stuff here, conversely i noticed that several of your links are broken.

thanks for that the content anyway!

# January 2, 2013 4:02 AM

Steed said:

I am always thought about this, appreciate it for putting

up.

# January 3, 2013 9:18 AM

Cason said:

I bookmared your site a few days ago coz your blog impresses

me.:,`,*

# January 17, 2013 1:32 PM

Simpson said:

This page truly has all of the information and facts I wanted concerning this subject and didn't know who to ask.

# February 13, 2013 9:10 PM

Minter said:

I am now not certain the place you're getting your information, however good topic. I needs to spend a while finding out much more or understanding more. Thanks for fantastic info I used to be on the lookout for this info for my mission.

# April 4, 2013 5:07 PM

Culbertson said:

What's up to every one, it's genuinely a nice for me to visit this web site, it

contains priceless Information.

# April 4, 2013 9:21 PM

Sturgis said:

My spouse and I stumbled over here by a different web address and thought I might check things out.

I like what I see so now i am following you. Look forward to exploring your web page yet

again.

# April 16, 2013 3:06 AM

Burk said:

However, this only removes the microbial acid

guard from the sebum and makes the skin more likely to get infected, causing the situation to worsen.

transporting fat-soluble vitamins A, D, E & K as well as converting carotene

to vitamin A. However, this is slowly changing especially when the benefits of

this natural oil is more widely seen in a lot

of individuals using it.

# April 23, 2013 9:03 PM