I've had quite the challenge in handling the following scenario.
- I want to use the Unity DI Framework to create a new channel for my service when needed.
- The service is secured with Federated security.
- The service is not called from within a service hosted within IIS, but called from within a self-hosted WCF service.
My current issue is occurring in step 3 above - how do I bootstrap the token and yet then satisfy the above 2 requirements also?? I'll outline the solutions to each step though as they're non-trivial and will hopefully assist someone else with this issue.
Solving issue 1: The following code-snippet will create a new instance of the channel anytime one is required.
container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannel()));
Here's some examples of people doing this better than I: http://unity.codeplex.com/discussions/211736 https://gist.github.com/tracker1/5675161
You can also use alternative lifetime managers as well e.g. the TransientLifetimeManager would work well here.
Solving Issue 2: Now the real difficulty begins - how do I include a security token with the InjectionFactory? Clearly I'm going to want to use CreatChannelWithIssuedToken, but I'm going to need to grab the bootstrap token to do so. This is fairly well documented around the net, e.g. here: http://www.cloudidentity.com/blog/2012/11/30/using-the-bootstrapcontext-property-in-net-4-5-2/ Some important things to note: make sure that you have a serviceBehaviors section in the config that specifies useIdentityConfiguration="true" otherwise your system.identityModel section will be ignored e.g.
<serviceBehaviors>
<behavior name="">
<serviceCredentials useIdentityConfiguration="true" />
</behavior>
</serviceBehaviors>
And then your system.identityModel section should also feature:
<system.identityModel>
<identityConfiguration>
<securityTokenHandlers>
<securityTokenHandlerConfiguration saveBootstrapContext="true" />
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
Then, provided that you've made a request to set this correctly on the session (see my other question: AJAX call against REST endpoint secured with Thinktecture's IdentityServer STS it will be available in the session whenever you access the bootstrap context security token like so:
var bootstrapContext = ((ClaimsIdentity) Thread.CurrentPrincipal.Identity).BootstrapContext
as BootstrapContext;
SecurityToken securityToken = bootstrapContext.SecurityToken;
You can then either change your InjectionFactory to look like this:
container.RegisterType<IControllerConfigurationService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x =>
{
var bootstrapContext = ((ClaimsIdentity)
Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
var securityToken = bootstrapContext.SecurityToken;
return new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannelWithIssuedToken(securityToken);
}));
Or perhaps better yet, create a class that inherits from ChannelFactory and add a method that pulls that combines the above logic into a single CreateChannelWithIssuedTokenUsingBootstrapContext method:
public class ChannelFactoryWithChannelFactoryOperations<T> : ChannelFactory<T>
{
protected ChannelFactoryWithChannelFactoryOperations(Type channelType) : base(channelType)
{
}
public ChannelFactoryWithChannelFactoryOperations()
{
}
public ChannelFactoryWithChannelFactoryOperations(string endpointConfigurationName)
: base(endpointConfigurationName)
{
}
public ChannelFactoryWithChannelFactoryOperations(string endpointConfigurationName,
EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress)
{
}
public ChannelFactoryWithChannelFactoryOperations(Binding binding) : base(binding)
{
}
public ChannelFactoryWithChannelFactoryOperations(Binding binding, string remoteAddress)
: base(binding, remoteAddress)
{
}
public ChannelFactoryWithChannelFactoryOperations(Binding binding, EndpointAddress remoteAddress)
: base(binding, remoteAddress)
{
}
public ChannelFactoryWithChannelFactoryOperations(ServiceEndpoint endpoint) : base(endpoint)
{
}
public T CreateChannelWithIssuedTokenUsingBootstrapContext()
{
var bootstrapContext =
((ClaimsIdentity) Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
SecurityToken securityToken = bootstrapContext.SecurityToken;
return CreateChannelWithIssuedToken(securityToken);
}
}
This allows you to then just call this:
container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannelWithIssuedTokenUsingBootstrapContext()));
Solving issue 3: Adding upon the complexity of the above two issues, I'm now trying the same thing outside of IIS in my own Self-Hosted WCF service. This same service is completely stateless, so here's where my next dilemma occurs: Bootstrapping of the security token still occurs, but it isn't occurring on the correct Thread. Unity seems to run its InjectionFactory in a separate Thread to the actual service call execution.
i.e. when the InjectionFactory delegate above executes, the CurrentPrincipal is an unauthorized GenericPrincipal. This is different from what we had in issue 2 above - where it is an authorized ClaimsPrincipal - I believe this is all set up by the IIS session (please do feel free to correct if I'm incorrect).
Funnily enough, if we then replace the above with
container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannel()));
i.e. now just inject an unsecured Channel object, we can then observe that in our self-hosted WCF service, where we actually try to interact with the channel, the Thread.CurrentPrincipal is the authenticated ClaimsPrincipal with the SecurityToken correctly Bootstrapped on the principal.
So the problem can be summarized as the following: because the InjectionFactory delegate executes on a thread on which no Authentication/Authorization has yet taken place, the SecurityToken is not actually available to pass to the creation of the channel.
Does anybody have any suggestions on how I might solve this issue? Have I already painted myself into a corner with this particular combination of self-hosted WCF and unity?
Thanks, Clint