Imagine a scenario like the following:

image

Trivial, if you are used to “Commands” and “Events”. Now imagine that the “SomethingExecutedEvent” is interesting for someone not in the same process of the WCF Back End, like this:

image

Now…in order to complicate things the requirement is that the Windows Service must know the security context of the WCF Back End or even worse must access other services using the same user identity that sent the “ExecuteSomethingCommand”.

NServiceBus has the ability to impersonate the sender of a message, but, IMHO, is a really basic implementation that simply cannot be used in production. What happens is that the bus simply puts in the message header the value of the Thread.CurrentPrincipal.Identity.Name property and, on the other side of the message handling pipeline, assigns to the Thread.CurrentPrincipal property of the working thread a new GenericPrincipal using the identity name found in the message headers.

Another kid on the block

Without entering in the detail of the default implementation we simply cannot rely on that implementation because we (our application) live in a federated environment based on Windows Identity Foundation and WS-Federation:

image

As you can see from the above figure the security token that identifies the caller is correctly attached (and verified) when the web application calls the WCF service but is not sent to the windows service attached to message so when the service tries  to call another service that relies on the same security token service the second call fails because the user is not authenticated anymore.

Options…

to solve the problem there are basically to options:

  1. Application Level Impersonation: the third party service authenticates the windows service using an internal/system user that is allowed to do everything and the call to the service brings with itself the identity of the original caller; it works, but you need a way to trust the sender of the NServiceBus message, otherwise anyone can send you a message issuing a request to do something on behalf of someone else; signing and encryption with a shared secret (read certificate) are there for you;
  2. Introduce WS-Federation in NServiceBus basically letting the original security token flow, with the message, from the sender of the message to the receiver and automatically impersonating, setting the correct ClaimsIdentity in the thread, the caller user;

Configuring NServiceBus

From the developer point of view the usage is really, really simple:

NServiceBus.Configure.WithWeb()
	.Log4Net()
	.CastleWindsorBuilder( _container )
	.DisableTimeoutManager()
	.XmlSerializer()
	.MsmqTransport()
	.IsTransactional( true )
	.PurgeOnStartup( false )
	.UnicastBus()
	.LoadMessageHandlers()
	.EnableWSFederation()
	.CreateBus()
	.Start();

The above is a standard NServiceBus configuration when used, typically as sender/publisher, in a web application. The only thing to notice is the EnableWSFederation extension method that disables the default impersonation mechanism and enables the sending of the WS-Federation security token. The method does really nothing interesting:

public static class ConfigureWSFederation
{
	static ConfigureWSFederation()
	{
		UseWSFederation = false;
	}
 
	public static ConfigUnicastBus EnableWSFederation( this ConfigUnicastBus config )
	{
		return EnableWSFederation( config, null );
	}
 
	public static ConfigUnicastBus EnableWSFederation( this ConfigUnicastBus config, String serviceConfigurationName )
	{
		config.ImpersonateSender( false );
 
		UseWSFederation = true;
		ServiceConfigurationName = serviceConfigurationName;
 
		return config;
	}
 
	public static bool UseWSFederation { get; private set; }
	public static String ServiceConfigurationName { get; private set; }
}

This is a standard NServiceBus configuration extension whose role is to capture the intent. the real work is done at runtime intercepting the message flow:

public class WSFederationManager : INeedInitialization, IMutateOutgoingTransportMessages
{
	void INeedInitialization.Init()
	{
		Configure.Instance.Configurer.ConfigureComponent<WSFederationManager>( DependencyLifecycle.SingleInstance );
		Configure.ConfigurationComplete += () => Configure.Instance.Builder.Build<ITransport>().TransportMessageReceived += OnTransportMessageReceived;
	}
 
	static void OnTransportMessageReceived( object sender, TransportMessageReceivedEventArgs e )
	{
		var _xmlDictionaryReaderQuotas = new XmlDictionaryReaderQuotas()
		{
			MaxArrayLength = 2097152,
			MaxStringContentLength = 2097152
		};
 
		var resultXml = e.Message.Headers[ "WS-Federation-Token" ];
		using( XmlDictionaryReader textReader = XmlDictionaryReader.CreateTextReader( Encoding.UTF8.GetBytes( resultXml ), _xmlDictionaryReaderQuotas ) )
		{
			var cfg = GetServiceConfiguration();
 			var token = cfg.SecurityTokenHandlers.ReadToken( textReader );
			var identities = cfg.SecurityTokenHandlers.ValidateToken( token );
 
			var p = ClaimsPrincipal.CreateFromIdentities( identities );
			Thread.CurrentPrincipal = p;
		}
	}
 
	static Microsoft.IdentityModel.Configuration.ServiceConfiguration GetServiceConfiguration()
	{
		var cfg = FederatedAuthentication.ServiceConfiguration;
		if( !String.IsNullOrWhiteSpace( ConfigureWSFederation.ServiceConfigurationName ) )
		{
			cfg = new Microsoft.IdentityModel.Configuration.ServiceConfiguration( ConfigureWSFederation.ServiceConfigurationName );
		}
 
		return cfg;
	}
 
	void IMutateOutgoingTransportMessages.MutateOutgoing( object[] messages, TransportMessage transportMessage )
	{
		var ci = ( ClaimsIdentity )Thread.CurrentPrincipal.Identity;
		var token = ci.BootstrapToken;
 
		using( var ms = new MemoryStream() )
		{
			using( XmlDictionaryWriter textWriter = XmlDictionaryWriter.CreateTextWriter( ms, Encoding.UTF8 ) )
			{
				var cfg = GetServiceConfiguration();
 
				cfg.SecurityTokenHandlers.WriteToken( textWriter, token );
 
				textWriter.Flush();
 
				var result = Encoding.UTF8.GetString( ms.ToArray() );
 
				if( transportMessage.Headers.ContainsKey( "WS-Federation-Token" ) )
					transportMessage.Headers.Remove( "WS-Federation-Token" );
 
				transportMessage.Headers.Add( "WS-Federation-Token", result );
			}
		}
	}
}

For simplicity I’ve removed all the error checks and all the validation logic required to understand if the WS-Federation security token must be attached to message, it’s just a bunch of ifs and try/catches.

This is it

the WSFederationManager class does 2 main things:

  1. implements the INeedInitialization interface in order to be plugged into the initialization pipeline of NServiceBus, in this stage:
    1. configure itself with the container as singleton;
    2. Attaches the ConfigurationComplete in order to be able to retrieve a reference to the ITransport implementation and attach an handler to the TransportMessageReceived event;
  2. implements the IMutateOutgoingTransportMessages interface in order to intercepts all outgoing messages to manipulate the message content;

This single implementation can be used both on the sender side and on the receiver side. The implementation is really straightforward:

Outgoing messages

For every single outgoing message we retrieve the current claims identity and the attached security token: in order to access the BootstrapToken property you need to set, in the application configuration file, the saveBootstrapTokens attribute:

<microsoft.identityModel>
	<service saveBootstrapTokens="true">

After having retrieved a reference to the security token we “simply” ask to the WIF configuration to serialize the current token. On the other side whenever a message is received we ask to the WIF configuration to de-serialize the token and after that we build the relative claim identity.

Warning!

I’m not a WIF expert so be aware that this implementation is a draft based on my current WIF knowledge, as soon as I’ll discover something new or, much worse, something wrong I’ll post an update here.

.m