Tuesday, November 12, 2013

How to programmatically configure WCF services and clients at runtime pt. 3.

In this post, we’ll go into some details about WCF and WIF (Windows Identity Foundation). I have to admit that I used some time to get the demo to work (with both azure  acs and ADFS). However, it seems that Azure ACS does have a big short coming for this type of scenario, it does not support ActAs (or identity delegation if you like). Which in a SoA context is vital to ensure end-to-end security for messages and features of the UI.

Please note that the code provided is _not_ production ready.

The two scenarios we are looking at:

1.       Identity federation: enable claims based authentication on wcf services by dynamically configuring WCF and WIF. Ideal for active clients like thick clients and background services.

2.       Identity delegation: allows a user identity to be reused by a trusted service account. Ideal for passive clients like web browsers.

Because ACS does not support ActAs we end up with a slightly more complex example that I initially imagined. Here is how this sample works:



The end user authenticates with Azure ACS (in the sample ACS is configured to authenticate with Facebook, google, Microsoft or Yahoo!) while the web server authenticates the service account with ADFS only. Both the user and the service account must have valid users that is authenticated.
Time for code (enough with the abstract stuff).

First we need to add a new binding builder for the secure binding.

internal class SecureBindingBuilder : IBindingBuilder
    {
        private bool IsClient;
 
        public Binding CreateBinding(string serviceName)
        {
            System.Net.ServicePointManager.ServerCertificateValidationCallback =
           ((sender, certificate, chain, sslPolicyErrors) => true);
            var secureBinding = new WS2007FederationHttpBinding(WSFederationHttpSecurityMode.TransportWithMessageCredential)
                                    {
                                        Name = "secure",
                                        TransactionFlow = false,
                                        HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
                                        MaxBufferPoolSize = int.Parse(GetWsValue(serviceName, "MaxBufferPoolSize")),
                                        MaxReceivedMessageSize = int.Parse(GetWsValue(serviceName, "MaxReceivedSize")),
                                        MessageEncoding = (WSMessageEncoding)Enum.Parse(typeof(WSMessageEncoding), GetWsValue(serviceName, "MessageFormat")),
                                        TextEncoding = Encoding.GetEncoding(GetWsValue(serviceName, "TextEncoding")),
                                        ReaderQuotas = XmlDictionaryReaderQuotas.Max,
                                        BypassProxyOnLocal = true,
                                        UseDefaultWebProxy = false,
                                    };
            SetSecuritySettings(serviceName, secureBinding);
            return secureBinding;
        }
 
        private void SetSecuritySettings(string serviceName, WSFederationHttpBinding secureBinding)
        {
            secureBinding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Basic256;
            secureBinding.Security.Message.NegotiateServiceCredential = false;
            secureBinding.Security.Message.EstablishSecurityContext = false;
            secureBinding.Security.Message.IssuedKeyType = SecurityKeyType.BearerKey;
            secureBinding.Security.Message.IssuedTokenType = null;
            EndpointIdentity identity = EndpointIdentity.CreateDnsIdentity((new Uri(AddressHelper.GetIssuerName(serviceName)).DnsSafeHost));
            secureBinding.Security.Message.IssuerAddress = CreateIssuerAddress(serviceName,identity);
            if (IsClient) secureBinding.Security.Message.IssuerBinding = CreateIssuerBinding(serviceName);
            else
                secureBinding.Security.Message.IssuerMetadataAddress = new EndpointAddress(new Uri(AddressHelper.GetStsMetadataAddress(serviceName)),identity);
        }
 
        private  EndpointAddress CreateIssuerAddress(string serviceName,EndpointIdentity identity)
        {
            if(IsClient)
                return new EndpointAddress(new Uri(AddressHelper.GetStsActAsAddress(serviceName)));
            return new EndpointAddress(new Uri(AddressHelper.GetIssuerName(serviceName)));
 
        }
 
        private static  Binding CreateIssuerBinding(string serviceName)
        {
            return BindingFactory.GetBindingBuilder("stsbinding").CreateBinding(serviceName);
        }
 
        private static string GetWsValue(string serviceName, string valueName)
        {
            var inheritFrom = GetSecureValue(serviceName, "InheritFrom");
            return ConfigurationReader.Reader.GetValue(ExpressionBuilder.GetEndpointConfigValue(serviceName, inheritFrom, valueName));
        }
 
        private static string GetSecureValue(string serviceName, string valueName)
        {
            return ConfigurationReader.Reader.GetValue(ExpressionBuilder.GetEndpointConfigValue(serviceName, "secure", valueName));
        }
 
        public void SetClientMode(bool isClient)
        {
            IsClient = isClient;
        }
    }

Note that we use the WS2007FederationHttpBinding which has most of the binding elements for WIF added. The first line in CreateBinding is added only to allow connections over SSL with self signed certificates, this should be removed in production code or made optional though the configuration framework.

For WIF to hook properly into the WCF framework we need to do some configuration on the service host. Create a helper method in HostFactory called Configure to make this independent of the service implementation.

public static void Configure(ServiceConfiguration config)
        {
            var serviceName = GetServiceName(config.Description.ServiceType);
            config.IdentityConfiguration = new IdentityConfiguration()
                                               {
                                                   TrustedStoreLocation = new StoreLocation(),
                                                   AudienceRestriction = { AudienceMode = AudienceUriMode.Always },
                                                   SaveBootstrapContext = true,
                                                   IssuerTokenResolver = new IssuerTokenResolver(),
                                                   CertificateValidationMode = X509CertificateValidationMode.None,
                                               };
            config.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(GetSecureValue(serviceName, "Audience")));
            config.IdentityConfiguration.IssuerNameRegistry = CreateIssuerNameRegistry(serviceName);
            config.UseIdentityConfiguration = true;
        }
 
        private static ConfigurationBasedIssuerNameRegistry CreateIssuerNameRegistry(string serviceName)
        {
            var registry = new ConfigurationBasedIssuerNameRegistry();
            registry.AddTrustedIssuer(GetSecureValue(serviceName, "Thumbprint"), GetSecureValue(serviceName, "IssuerName"));
            return registry;
        }

 We need to run this code from the service implementation (to me it seems that this is convention based) by adding the following code in DemoService.svc.cs

        {
            HostFactory.Configure(config);
        }

When this is done we need some way to get security tokens from ADFS. By isolating the needed code in a TokenManager class, we reduce the number of changes needed in the ServiceProxyContainer class.

public class TokenManager
    {
        private readonly string ServiceName;
        private readonly string UserName;
        private readonly string Password;
        private readonly SecurityToken BootstrapToken;
 
        public TokenManager(string serviceName, string username,string password)
        {
            UserName = username;
            Password = password;
            ServiceName = serviceName;
        }
 
        public TokenManager(string serviceName,SecurityToken bootstrapToken, string username, string password)
        {
            UserName = username;
            Password = password;
            ServiceName = serviceName;
            BootstrapToken = bootstrapToken;
        }
 
        public SecurityToken GetToken()
        {
            var channel = CreateChannel();
            var rst = CreateIssueNewRequest();
            return GetSecurityToken(channel, rst);
        }
 
        private RequestSecurityToken CreateIssueNewRequest()
        {
            var rst = new RequestSecurityToken
                          {
                              RequestType = RequestTypes.Issue,
                              KeyType = KeyTypes.Bearer,
                              AppliesTo =
                                  new EndpointReference(
                                  ConfigurationReader.Reader.GetValue(
                                      ExpressionBuilder.GetEndpointConfigValue(
                                          ServiceName, "secure", "Address")))
                          };
            return rst;
        }
 
        private static SecurityToken GetSecurityToken(WSTrustChannel channel, RequestSecurityToken rst)
        {
            RequestSecurityTokenResponse rstr = null;
            var token = channel.Issue(rst, out rstr);
            return token;
        }
 
        private WSTrustChannel CreateChannel()
        {
            var stsBinding = CreateStsBinding();
            var trustChannelFactory = new WSTrustChannelFactory(stsBinding, AddressHelper.GetStsAddress(ServiceName))
                                          {
                                              TrustVersion =
                                                  TrustVersion
                                                  .WSTrust13
                                          };
            trustChannelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
                X509CertificateValidationMode.None;
            trustChannelFactory.Credentials.UserName.UserName = UserName;
            trustChannelFactory.Credentials.UserName.Password = Password;
            trustChannelFactory.Endpoint.Behaviors.Add(new MustUnderstandBehavior(false));
            var channel = (WSTrustChannel)trustChannelFactory.CreateChannel();
            return channel;
        }
 
        private Binding CreateStsBinding()
        {
            return BindingFactory.GetBindingBuilder("stsbinding").CreateBinding(ServiceName);
        }
 
        public SecurityToken GetTokenOnBehalfOf()
        {
            var channel = CreateChannel();
            var rst = CreateDelegateTokenRequest();
            return GetSecurityToken(channel, rst);
        }
 
        private RequestSecurityToken CreateDelegateTokenRequest()
        {
            var rst = new RequestSecurityToken
                          {
                              RequestType = RequestTypes.Issue,
                              KeyType = KeyTypes.Bearer,
                              AppliesTo =
                                  new EndpointReference(
                                  ConfigurationReader.Reader.GetValue(
                                      ExpressionBuilder.GetEndpointConfigValue(
                                          ServiceName, "secure", "Address"))),
                              ActAs = new SecurityTokenElement(BootstrapToken)
                          };
            return rst;
        }
    }         

As you can see, we reuse the binding builder for the STS binding both in the client and the service. This set up the communication with, in this case, ADFS.

Create a new channel factory based on this binding, and set the client credentials. Regardless whether we are using delegate tokens or direct logon the channel will be the same. In the sample code we do not perform certificate validation. After the channel is configured and created, we create a request and call the Issue method on the channel. The token we receive will be used when we create the service channel in the container.
As a last step we need to modify the ServiceProxyContainer:

Add to methods to get security tokens:

private SecurityToken GetToken()
        {
            var manager = new TokenManager(ServiceName, UserName, Password);
            return manager.GetToken();
        }
 
        private SecurityToken GetTokenOnBehalfOf()
        {
            var manager = new TokenManager(ServiceName,GetTokenFromBootstrap(), UserName, Password);
            return manager.GetTokenOnBehalfOf();
        }

 

And create the channel that uses the issued security token:

public T GetClient()
        {
            if (Client == null)
            {
                if (BootstrapContext != null) Client = ClientFactory.CreateChannelWithIssuedToken(GetTokenOnBehalfOf());
                else if (UseSecureChannel) Client = ClientFactory.CreateChannelWithIssuedToken(GetToken());
                else Client = ClientFactory.CreateChannel(new EndpointAddress(Url));
            }
            return Client;
        }

Handling the tokens in this way makes it a lot easier to do debugging and to inspect the tokens before they are sent to the service.

This is basically it.

I have added a simple MVC app to show delegation in action. While the console application is mainly the same.

Some help with setting up ADFS to forward name identifier in the issued token.
http://social.msdn.microsoft.com/Forums/vstudio/en-US/2fbd4acf-358e-4bf3-abf6-fc2c9daa20ca/incoming-name-id-format-for-incoming-claim-type-name-id-in-adfs-v20

I hope you enjoyed these posts, I might add some samples on MSMQ and Net.TCP if you are interested. However, it should be easy to add this using the sample code provided as a template.

Wednesday, September 18, 2013

Wcf, wif and azure acs

After struggling with federated identity with wcf services for a couple of days I finally got it to work. The documentation for wif 4.5 was, to say the least, really thin. Got some tips from tinktecture and other blogs and are ready to do the next post in the Wcf series. Hope you find it useful

Wednesday, September 11, 2013

How to programmatically configure WCF services and clients at runtime pt. 2.


In this post we will add support for a more advanced configuration management and really take control of WCF. We will also add support for REST service endpoints.

Centralized configuration – the simple version

For our “late configuration” of WCF, we want to add a shared configuration store that all components in our system utilizes. In this example, we will use XML and xpath to create a versioned configuration store with inheritance. With inheritance, we mean that data is inherited from the parent data set unless it is overridden in the children. A configuration set represents a runtime environment like dev, test or prod.

The basic structure
The xml file consists of one or more ConfigurationSet, which may contain as many services as needed. A service consist of one or more endpoints with the appropriate binding configuration settings.



When we are reading from the file we use the value from the first set that has a value in the requested node.

public string GetValue(string expression)
        {
            return GetValueFromVersion(expression, Version);
        }
 
private string GetValueFromVersion(string expression, string version)
        {
            if (string.IsNullOrEmpty(version)) return null;
            var expr = CreatExpression(expression, version);
            var iterator = Navigator.Select(expr);
            if (iterator.MoveNext())
                return iterator.Current.Value;
            if (expression == "ParentSet") return null;
            return GetValueFromVersion(expression, GetParent(version));
        }
 
public IEnumerable GetValues(string expression)
        {
            var expr = CreatExpression(expression, Version);
            var iterator=Navigator.Select(expr);
            var list=new List();
            while (iterator.MoveNext())
            {
                list.Add(iterator.Current.Value);
            }
            return list;
        }
 
private XPathExpression CreatExpression(string expression, string version)
        {
            var expr = Navigator.Compile(FormatExpression(expression, version));
            return expr;
        }
 
//Creates an xpath expression by combining an incoming expression part with the set selector query
private static string FormatExpression(string expression, string version)
        {
            var expr = string.Format("ConfigurationSets/ConfigurationSet[SetName='{0}']/{1}", version, expression);
            return expr;
        }
 

To make it easy to create the needed expressions we create our own little expression builder.

public static class ExpressionBuilder
    {
        public static string GetEndpointNames(string serviceName)
        {
            return string.Format("Services/EndpointConfig[ServiceName='{0}']//Endpoint/EndpointName", serviceName);
        }
 
        public static string GetEndpointConfigValue(string serviceName, string bindingName, string attributeName)
        {
            return string.Format("Services/EndpointConfig[ServiceName='{0}']//Endpoint[EndpointName='{1}']/{2}", serviceName, bindingName, attributeName);
        }
 
        public static string GetClientEndpoint(string serviceName)
        {
            return string.Format("Services/EndpointConfig[ServiceName='{0}']/ActiveEndpoint", serviceName);
        }
    }

With this, we have the necessary code to read config settings and use it in our configuration code.
 First we create a helper class that loads and initializes our configuration store. Note that this helper should include some kind of caching mechanism that allows the application to get new settings without restart.

internal static class ConfigurationReader
    {
        internal static VersionedDocumentReader Reader;
 
        static ConfigurationReader()
        {
            Reader = new VersionedDocumentReader();
            Reader.Load(ConfigurationManager.AppSettings["ConfigPath"]);
            Reader.SetVersion(ConfigurationManager.AppSettings["ConfigSet"]);
        }
    }

To make it easier to configure and name the services we add a new attribute that sets the service name on the interface. We access this attribute instance at runtime using reflection and read the service name. If the service interface doesn’t have this attribute set we use the interface name as the service name.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = true)]
    public sealed class ServiceNameAttribute : Attribute
    {
        public string ServiceName { get; private set; }
 
        public ServiceNameAttribute(string serviceName)
        {
            ServiceName = serviceName;
        }
    }

Changes to the code.

We need to pass the service name to the binding creators so they can find the correct settings to use during configuration of the binding.

public interface IBindingBuilder
    {
        Binding CreateBinding(string serviceName);
    }

Then we add a helper method to the binding builders to read data from the configuration store and inject the binding name.

private static string GetValue(string serviceName,string valueName)
        {
            return ConfigurationReader.Reader.GetValue(ExpressionBuilder.GetEndpointConfigValue(serviceName, "basic", valueName));
        }


public Binding CreateBinding(string serviceName)
        {
            return new BasicHttpBinding
            {
                Name = "basic",
                AllowCookies = false,
                HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
                MaxBufferPoolSize = int.Parse(GetValue(serviceName,"MaxBufferPoolSize")),
                MaxReceivedMessageSize = int.Parse(GetValue(serviceName, "MaxReceivedSize")),
                MessageEncoding = (WSMessageEncoding)Enum.Parse(typeof(WSMessageEncoding), GetValue(serviceName,"MessageFormat")),
                TextEncoding = Encoding.GetEncoding(GetValue(serviceName, "TextEncoding")),
                ReaderQuotas = XmlDictionaryReaderQuotas.Max,
                BypassProxyOnLocal = true,
                UseDefaultWebProxy = false
            };
        }

And we add a new class for creating a REST binding

internal class RestBindingBuilder:IBindingBuilder
    {
        public Binding CreateBinding(string serviceName)
        {
            var binding = new WebHttpBinding(WebHttpSecurityMode.None)
                              {
                                  AllowCookies = false,
                                  HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
                                  MaxBufferPoolSize = int.Parse(GetValue(serviceName, "MaxBufferPoolSize")),
                                  MaxReceivedMessageSize = int.Parse(GetValue(serviceName, "MaxReceivedSize"))
                              };
            return binding;
        }
 
        private static string GetValue(string serviceName, string valueName)
        {
            return ConfigurationReader.Reader.GetValue(ExpressionBuilder.GetEndpointConfigValue(serviceName, "rest", valueName));
        }
    }

For the WebHttpBinding to work we need to do some additional changes to the code, I will comment this when we get to these parts later.
We also need to read the address from the configuration store by changing the AddressHelper class to take the service name and use this to look for the value in the xml file.

internal static string GetFormattedAddress(string bindingType,string serviceName)
        {
            return String.Format(ConfigurationReader.Reader.GetValue(ExpressionBuilder.GetEndpointConfigValue(serviceName, bindingType, "Address"))) + "/" + bindingType;
        }

Update ServiceConfigurationImp to get the service name and pass it to the binding builders.
Add a method called GetServiceName. You need to get the service interface from the service type. I have done this the quick and dirty way her and assuming that the serviceType only implements one service interface. It the interface is decorated with the ServiceName attribute we use the value from it, else we use the name of the serviceType.

private string GetServiceName(Type serviceType)
        {
            var attrib = serviceType.GetInterfaces()[0].GetAttribute();
            if (attrib == null) return serviceType.Name;
            return attrib.ServiceName;
        }

 

Update the GetBindingTypes to read from the configuration store and not the config file as in the previous post.

private static IEnumerable GetBindingTypes(string serviceName)
        {
            var bindingTypes = ConfigurationReader.Reader.GetValues(ExpressionBuilder.GetEndpointNames(serviceName));
            return bindingTypes;
        }

Update CreateEndpoint to support WebHttpBinding, where we set the default response format to json.

private static ServiceEndpoint CreateEndpoint(Binding binding, EndpointAddress address, ContractDescription cd)
        {
            if (binding is WebHttpBinding)
                return new WebHttpEndpoint(cd, address)
                           {
                               HelpEnabled = true,
                               DefaultOutgoingResponseFormat = WebMessageFormat.Json,
                               Binding = binding
                           };
            return new ServiceEndpoint(cd, binding, address);
        }

This is basically the changes needed for the service side of WCF.
On the client side we only need to do one small change in ServiceClientContainer. It also needs to get the service name from the attribute and we need to get the active client binding from the configuration store.

private string GetServiceName()
        {
            var serviceType = typeof(T);
            var attrib = serviceType.GetAttribute();
            if (attrib == null) return serviceType.Name;
            return attrib.ServiceName;
        }

 
internal ServiceClientContainer()
        {
            ServiceName = GetServiceName();
            BindingType = GetBindingType();
            Url = AddressHelper.GetFormattedAddress(BindingType,ServiceName);
        }

 To enable support for rest we change the Initialize method

public ServiceClientContainer Initialize()
        {
            ClientFactory = CreateChannelFactory(ServiceName, Url);
            if (ClientFactory.Endpoint.Binding is WebHttpBinding)
                ClientFactory.Endpoint.Behaviors.Add(new WebHttpBehavior());
            return this;
        }

Updating the config files
There are just a few things we need to change in the config files for the client and service. We need to add references to the location of the xml configuration file and which set to use.

    <add key="ConfigPath" value="C:\Settings\sentralConfiguration.xml"/>
    <add key="ConfigSet" value="Demo"/>

Download the code here: http://jmp.sh/b/k0b3LRA0jxOz83L4baQ8