WCF custom Behavior to authenticate against Azure Access Control

In a recent post, we have realized a simple scenario that use ACS with a Token Request with a UserName/Password (Symmetric Key) on client side and a Token Validation on server side when it receive the request.

Because I’m a lazy man, I don’t want to write some code to implement the Token Request (it’s also because I’m using BizTalk Server and I don’t want to change my code when using ACS, I just want to use the power of WCF that consist of use extensions and parameter in the Port Configuration).

So I decide to create a Custom WCF Behavior that allow me to configure the ACS Token Request in a config file.

For this, I’ve read some article about the subject :

And so I create this three class :

  • BizTalkWCFACS : this class derive from System.ServiceModel.Dispatcher.IClientMessageInspector, this is the Interface for Client side interaction. This class allows us to interact with the message with two method :
    • BeforeSendRequest : we use this method in our case to add header to the message
    • AfterSendReply : not use in our case
  • CustomBehaviorElement : this class derive from System.ServiceModel.Configuration.BehaviorExtensionElement. This class is used to define the property of our extension. We can retrieve this property in the config file, and we can define the default value, required attribute and so more. Also in this class we initialize the CustomBehavior with the CreateBehavior Method.
  • CustomBehavior : this class derive from System.Attribute and System.ServiceModel.Configuration.IEndpointBehavior. This class is used to Apply the behavior :
    • ApplyClientBehavior on client side (we use this method)
    • ApplyDispatchBehavior on service side (not use yet)

So here is the code :

BizTalkWCFACS Class

public class BizTalkWCFACS : IClientMessageInspector
    {
        private string _issuerName;
        private string _issuerKey;
        private string _serviceNamespace;
        private string _acsHostName;
        private string _serviceAddress;
        public BizTalkWCFACS()
        { }

        public BizTalkWCFACS(string issuerName, string issuerkey, string serviceNamespace, string acsHostName, string serviceAddress)
        {
            this._issuerKey = issuerkey;
            this._issuerName = issuerName;
            this._serviceNamespace = serviceNamespace;
            this._serviceAddress = serviceAddress;
            this._acsHostName = acsHostName;
        }

        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
           // throw new NotImplementedException();
        }

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
        {
            string acsToken = string.Empty;

            try
            {
                acsToken = GetACSToken(_issuerName,_issuerKey,_serviceAddress,_serviceNamespace,_acsHostName);
            }
            catch
            {
            }
            HttpRequestMessageProperty property;

            string authHeaderValue = string.Format("WRAP access_token=\"{0}\"", HttpUtility.UrlDecode(acsToken));

            if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name))
            {
                property = (HttpRequestMessageProperty)(request.Properties[HttpRequestMessageProperty.Name]);
               

            }
            else
            {
                property = new HttpRequestMessageProperty();
                request.Properties.Add(HttpRequestMessageProperty.Name, property);

            }

            property.Headers.Add(HttpRequestHeader.Authorization, authHeaderValue);
           
            return null;
        }
        private static string GetACSToken(string IssuerName, string IssuerKey, string ServiceAddress, string ServiceNamespace, string AcsHostName)
        {
            // request a token from AppFabric AC
            WebClient client = new WebClient();
            client.BaseAddress = string.Format("<a href="https://%7b0%7d.%7b1/">https://{0}.{1</a>}", ServiceNamespace, AcsHostName);

            NameValueCollection values = new NameValueCollection();
            values.Add("wrap_name", IssuerName);
            values.Add("wrap_password", IssuerKey);
            values.Add("wrap_scope", ServiceAddress);

            byte[] responseBytes = client.UploadValues("WRAPv0.9", "POST", values);

            string response = Encoding.UTF8.GetString(responseBytes);

            return response
                .Split('&')
                .Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
                .Split('=')[1];
        }

    }

CustomBehaviorExtensionElement Class

public class CustomBehaviorExtensionElement : BehaviorExtensionElement
    {

        public override Type BehaviorType
        {
            get { return typeof(CustomBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new CustomBehavior(issuerName, issuerKey, realm, serviceNamespace, accessControlHostName);
        }

        #region properties

        //Define class properties to represent configuration element attributes.
        //This is only used if we are
        [ConfigurationProperty("issuerName", DefaultValue = "", IsRequired = true)]
        public string issuerName
        {
            get
            {
                return (string)base["issuerName"];
            }
            set { base["issuerName"] = value; }
        }

        [ConfigurationProperty("issuerKey", DefaultValue = "", IsRequired = true)]
        public string issuerKey
        {
            get
            {
                return (string)base["issuerKey"];
            }
            set { base["issuerKey"] = value; }
        }

        [ConfigurationProperty("realm", DefaultValue = "", IsRequired = true)]
        public string realm
        {
            get
            {
                return (string)base["realm"];
            }
            set { base["realm"] = value; }
        }

        [ConfigurationProperty("accessControlHostName", DefaultValue = "", IsRequired = true)]
        public string accessControlHostName
        {
            get
            {
                return (string)base["accessControlHostName"];
            }
            set { base["accessControlHostName"] = value; }
        }

        [ConfigurationProperty("serviceNamespace", DefaultValue = "", IsRequired = true)]
        public string serviceNamespace
        {
            get
            {
                return (string)base["serviceNamespace"];
            }
            set { base["serviceNamespace"] = value; }
        }

        //Copies the content of the specified configuration element to this configuration element
        public override void CopyFrom(ServiceModelExtensionElement extFrom)
        {
            base.CopyFrom(extFrom);
            CustomBehaviorExtensionElement element = (CustomBehaviorExtensionElement)extFrom;
            issuerName = element.issuerName;
            issuerKey = element.issuerKey;
            realm = element.realm;
            serviceNamespace = element.serviceNamespace;
            accessControlHostName = element.accessControlHostName;
           
        }
        //Represents a collection of configuration-element properties
        private ConfigurationPropertyCollection _properties;

        /// Both properties are returned as a collection.
        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                if (_properties == null)
                {
                    _properties = new ConfigurationPropertyCollection();
                    _properties.Add(new ConfigurationProperty("issuerName", typeof(string), "", ConfigurationPropertyOptions.IsRequired));
                    _properties.Add(new ConfigurationProperty("issuerKey", typeof(string), "", ConfigurationPropertyOptions.IsRequired));
                    _properties.Add(new ConfigurationProperty("realm", typeof(string), "", ConfigurationPropertyOptions.IsRequired));
                    _properties.Add(new ConfigurationProperty("serviceNamespace", typeof(string), "", ConfigurationPropertyOptions.IsRequired));
                    _properties.Add(new ConfigurationProperty("accessControlHostName", typeof(string), "", ConfigurationPropertyOptions.IsRequired));
                }
                return _properties;
            }
        }

        #endregion
    }

CustomBehavior Class

public class CustomBehavior : Attribute, IEndpointBehavior
    {
        private string _issuerName;
        private string _issuerKey;
        private string _serviceAddress;
        private string _serviceNamespace;
        private string _accessControlHostName;
       
        public CustomBehavior()
        {
        }

        public CustomBehavior(string issuerName,string issuerKey, string serviceAddress, string serviceNamespace, string accessControlHostName)
        {
            _issuerName= issuerName;
            _issuerKey= issuerKey;
            _serviceAddress= serviceAddress;
            _serviceNamespace=serviceNamespace;
            _accessControlHostName =accessControlHostName;

        }
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {

            //throw new NotImplementedException();
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
         //   throw new NotImplementedException();
            BizTalkWCFACS clientInspector = new BizTalkWCFACS(_issuerName,_issuerKey,_serviceNamespace,_accessControlHostName,_serviceAddress);
            clientRuntime.MessageInspectors.Add(clientInspector);
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            throw new NotImplementedException();
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            //throw new NotImplementedException();
        }
    }

So after, you need to add this few line in your config file :

<client>
      <endpoint name="MyEndpoint"
          address="<a href="http://localhost:7000/Service/Default.aspx">http://localhost:7000/Service/Default.aspx</a>"
          binding="basicHttpBinding"
          contract="MyInterface"
          behaviorConfiguration="MyBehavior" />
    </client>

And finally, you can use this custom behavior only with a configuration :

<behaviors>
      <endpointBehaviors>
        <behavior name="MyBehavior">
          <customBehavior
            issuerName="MyIssuerName"
            issuerKey="MyIssuerKey"
            realm="MyRealm"
            serviceNamespace="MyNamespace"
            accessControlHostName="accesscontrol.windows.net"/>
        </behavior>
      </endpointBehaviors>
    </behaviors>

The property are :

  • issuerName : the name of your Service Name Identity defined in the Azure Control Service Portal
  • issuerKey : The key defined for the service Identity, (of type password)
  • realm : the realm defined for the Service Name Identity defined in the Rule Party Application Section
  • serviceNamespace : the namespace of the your acs subscription, you can see this namespace

image

In BizTalk Server, you can add an extension behavior for Custom WCF Bindings :

image

Right click to Add an Extension :

image

Select the Behavior (here azurefactoryACSBehavior) :

image

Configure the Property

image

And Voila!!!!

Have fun!

This entry was posted in Azure, Microsoft, WCF and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s