Pass-through SAML tokens & Secure Conversation (Part2)
Few people have contacted me regarding follow-up on my last post so this post continues where the last one left off.
To keep the API simple, I have created few extension methods. Let’s first review these.
public static class Extensions
{
public static void EnableSamlTokenCaching(this ServiceHostBase source)
{
var orignal = source.Description.Behaviors.Remove<ServiceCredentials>();
source.Description.Behaviors.Add(new ServiceCredentialsEx(orignal));
}
public static SamlSecurityToken GetIncommingSamlToken(this ServiceSecurityContext source)
{
var policy = source.AuthorizationPolicies.OfType<SamlTokenCachingPolicy>().FirstOrDefault();
if (policy != null)
return policy.IncommingSamlToken;
return null;
}
public static void EnableSamlTokenFlow<TChannel>(this ClientBase<TChannel> source, SamlSecurityToken tokenToFlow) where TChannel : class
{
var orignal = source.Endpoint.Behaviors.Remove<ClientCredentials>();
source.Endpoint.Behaviors.Add(new ClientCredentialsWrapper(orignal));
}
}
EnableSamlTokenCaching is what you have to call to enable SAML token caching functionality for a particular ServiceHost. Here is an example of a simple host (created using a custom factory (IIS/WAS hosted scenario)) being configured with this API
public class EchoServiceHostFactory : ServiceHostFactoryBase
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var sh = new ServiceHost(typeof(EchoService), baseAddresses);
sh.EnableSamlTokenCaching();
return sh;
}
}
The highlighted call simply injects couple of customized components (interceptors) into WCF’s security framework. Specifically it is injecting a new customized SamlSecurityTokenAuthenticator, which wraps the original authenticator and simply delegates all calls to it.
SamlSecurityTokenAuthenticator is called during secure conversation handshake. Here our custom authenticator (SamlSecurityTokenAuthenticatorEx) enables us to get hold of the incoming SAML token etc.
public class ServiceCredentialsEx : ServiceCredentials
{
public ServiceCredentialsEx(ServiceCredentials other)
: base(other){}
public override SecurityTokenManager CreateSecurityTokenManager()
{
return new ServiceCredentialsSecurityTokenManagerEx(this);
}
protected override ServiceCredentials CloneCore()
{
return new ServiceCredentialsEx(this);
}
}
public class ServiceCredentialsSecurityTokenManagerEx : ServiceCredentialsSecurityTokenManager
{
public ServiceCredentialsSecurityTokenManagerEx(ServiceCredentials parent)
: base(parent){}
public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
{
var authenticator = base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
if (authenticator is SamlSecurityTokenAuthenticator)
return new SamlSecurityTokenAuthenticatorEx((SamlSecurityTokenAuthenticator)authenticator);
return authenticator;
}
}
Inside SamlSecurityTokenAuthenticatorEx, I’m simply creating an additional token caching policy and adding it to the collection of policies created by default SAML token authenticator.
public class SamlSecurityTokenAuthenticatorEx : SamlSecurityTokenAuthenticator
{
SamlSecurityTokenAuthenticator wrapped;
public SamlSecurityTokenAuthenticatorEx(SamlSecurityTokenAuthenticator wrapped)
: base(new List<SecurityTokenAuthenticator>())
{
this.wrapped = wrapped;
}
protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateTokenCore(SecurityToken token)
{
var orignalPolicies = wrapped.ValidateToken(token);
List<IAuthorizationPolicy> finalPolicies = new List<IAuthorizationPolicy>(orignalPolicies);
finalPolicies.Add(new SamlTokenCachingPolicy((SamlSecurityToken)token));
return finalPolicies.AsReadOnly();
}
}
My SamlTokenCachingPolicy is just a wrapped around the cached token.
public class SamlTokenCachingPolicy : IAuthorizationPolicy
{
public const string PolicyID = "{32835E28-3ED4-42d4-A2EA-FA71E13AF51F}";
public SamlTokenCachingPolicy(SamlSecurityToken token)
{
this.IncommingSamlToken = token;
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
return true;
}
public ClaimSet Issuer
{
get { throw new System.NotImplementedException(); }
}
public string Id
{
get { return PolicyID; }
}
public SamlSecurityToken IncommingSamlToken { get; private set; }
}
IAuthorizationPolicy model nicely aligns with WCF security framework (so I have used it here). If you are not comfortable with this approach, you could get the same results by choosing a different caching strategy.
Inside your service method you can get hold of the incoming SAML token using following piece of code.
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Prefix)]
public class EchoService : IEchoService
{
public string Echo(string input)
{
// Get token from incoming security context..
var samlToken = OperationContext.Current.ServiceSecurityContext.GetIncommingSamlToken();
}
}
GetIncommingSamlToken is again an extension which extracts the actual token from my custom AuthorizationPolicy(SamlTokenCachingPolicy).
public static SamlSecurityToken GetIncommingSamlToken(this ServiceSecurityContext source)
{
var policy = source.AuthorizationPolicies.OfType<SamlTokenCachingPolicy>().FirstOrDefault();
if (policy != null)
return policy.IncommingSamlToken;
return null;
}
Oh, this post is already way too long. Probably next time I will show you, how to pass this token to backend services (sharing the same certificate).