Monday, October 31, 2016

Using BizTalk Behavior Extension to log in to CRM Online


Overview


This is a hands on example about creating, deploying and using BizTalk behavior extension to log in to CRM Online. You need Azure Application ID for the client (BizTalk) and CRM username and password.

The beef of the behavior extension is that it adds or creates an authorization header with a value "Bearer + [token returned from CRM]". The authorization header is then used when the integration tries to connect to the backend REST API address.

Instructions


In Visual Studio create a new C# Class Library project.

Install ADAL to Visual Studio (https://azure.microsoft.com/en-us/documentation/articles/active-directory-authentication-libraries/).

Go to Tools -> NuGet Package Manager -> Package Manager Console
Run "Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory" command.



Add references needed by the project.



Add the code.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Diagnostics;
using System.ServiceModel;
using System.Web;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.Configuration;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace AppIDAuthentication
{
    class AppIDAuthenticationBehavior : IEndpointBehavior
    {

        public string Username { get; set; }
        public string Password { get; set; }
        public string CrmUri { get; set; }
        public string ClientID { get; set; }

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {

        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            
            AppIDAuthenticationMessageInspector inspector = new AppIDAuthenticationMessageInspector(this.Username, this.Password, this.CrmUri, this.ClientID);
            clientRuntime.MessageInspectors.Add(inspector);
            
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {

        }

        public void Validate(ServiceEndpoint endpoint)
        {

        }
    }

    public class AppIDAuthenticationMessageInspector : IClientMessageInspector
    {
        string _username = string.Empty;
        string _password = string.Empty;
        string _crmUri = string.Empty;
        string _clientID = string.Empty;

        private string m_Bearer;
        private const string AUTHORIZATION_HTTP_HEADER = "Authorization";
        
        public AppIDAuthenticationMessageInspector(string username, string password, string crmUri, string clientID)
        {
            this._username = username;
            this._password = password;
            this._crmUri = crmUri;
            this._clientID = clientID;
        }

        //Enables inspection or modification of a message after a reply message is received but prior to passing it back to the client application.
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            //Do nothing
        }
          
        //Enables inspection or modification of a message before a request message is sent to a service.
        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request,
            System.ServiceModel.IClientChannel channel)
        {

            Task<Task> CRMLogin = new Task<Task>(async () =>
            {

                AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/common", false);

                AuthenticationResult result = await authContext.AcquireTokenAsync(_crmUri, _clientID, new UserPasswordCredential(_username, _password));

                m_Bearer = "Bearer " + result.AccessToken;

            });

            CRMLogin.Start();
            CRMLogin.Wait();
            CRMLogin.Result.Wait();
            
            HttpRequestMessageProperty httpRequestMessage;

            object httpRequestMessageObject;

            if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
            {
                httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
                if (string.IsNullOrEmpty(httpRequestMessage.Headers[AUTHORIZATION_HTTP_HEADER]))
                {
                    httpRequestMessage.Headers[AUTHORIZATION_HTTP_HEADER] = this.m_Bearer;
                }
            }

            else
            {
                httpRequestMessage = new HttpRequestMessageProperty();
                httpRequestMessage.Headers.Add(AUTHORIZATION_HTTP_HEADER, this.m_Bearer);
                request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
            }

            return null;

        }

    }    
  
    public class AppIDAuthenticationBehaviorExtensionElement : BehaviorExtensionElement
    {
        [ConfigurationProperty("username", DefaultValue = "", IsRequired = true)]
        public string Username
        {
            get { return (string)base["username"]; }
            set { base["username"] = value; }
        }

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

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

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

        protected override void Init()
        {
            base.Init();
        }
        protected override void InitializeDefault()
        {
            base.InitializeDefault();
        }


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

        protected override object CreateBehavior()
        {
            AppIDAuthenticationBehavior behavior = new AppIDAuthenticationBehavior();
            behavior.Username = this.Username;
            behavior.Password = this.Password;
            behavior.CrmUri = this.CrmUri;
            behavior.ClientID = this.ClientID;

            return behavior;
        }
    }

}

Add the dlls (all three in the picture, your dll might have a different name) to the GAC using "gacutil -i" command.









You can use SvcConfigEditor ("C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\SvcConfigEditor.exe") to add behavior element extension to the machine.config file ("C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config").

From "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config" you can then manually copy the new line to the "C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config" if needed.



You could select the assembly from GAC also.










In the BizTalk Administrator Console -> integration Send Port -> WCF-WebHttp Transport Properties define the security mode as 'Transport' and the client credential type as 'None' to enable HTTPS in the backend address.




Suppress body for verbs: GET


You need to increase maximum received message size.



Add the extension you created



Enter the values



Enjoy!