You may be in the scenario where you need your CRM Online environment to access a web-service which is hosted in Azure via a Plugin or Workflow Assembly.

What is ADAL?

Traditionally if we were working with a .NET web application we could very easily authenticate our caller using the Azure AD Authentication Library (ADAL) for .NET.  Microsoft describes ADAL as follows:

The Azure AD Authentication Library (ADAL) for .NET enables client application developers to easily authenticate users to cloud or on-premises Active Directory (AD), and then obtain access tokens for securing API calls. ADAL for .NET has many features that make authentication easier for developers, such as asynchronous support, a configurable token cache that stores access tokens and refresh tokens, automatic token refresh when an access token expires and a refresh token is available, and more. By handling most of the complexity, ADAL can help a developer focus on business logic in their application and easily secure resources without being an expert on security,

ADAL works very well, and as Microsoft mention you delegate much of the complex security considerations to them when using the library for authentication. The only catch for us in a CRM Online sandboxed environment, is that ADAL doesn’t work!

The Scenario

Lets consider the following scenario for the purpose of this post:

We have a Restful WebAPI service hosted in Azure which accepts a PUT HTTP request and serves the function of simply dropping a message on a queue somewhere. This webservice uses OAUTH for authentication.

In a non-sandboxed environment or plugin mode we would include the ADAL libraries to our plugin project, utilize the ADAL methods of constructing an OAUTH authentication token and simply create a request to our web-service. Also important to note that instead of deploying the ADAL dll (Microsoft.IdentityModel.Clients.ActiveDirectory.dll) to the server GAC or bin as you would on-premise,  you would have to ILMerge the ADAL dll and Plugin dll for deployment to a CRM Online environment.

The following is a code snippet of the Execute method and related functions in the Plugin which would send the request to the webservice using the ADAL library and method of authentication

<pre>protected void ExecutePutWebservice(LocalPluginContext localContext)
{
    if (localContext == null)
    {
        throw new ArgumentNullException("localContext");
    }
 
    Microsoft.Xrm.Sdk.IPluginExecutionContext context = localContext.PluginExecutionContext;
 
    try
    {
        var service = localContext.OrganizationService;
 
        var url = "https://mydomain.com/webservice/putmessage/";
 
        PutMessage(url);
    }
    catch (FaultException ex)
    {
        throw new InvalidPluginExecutionException(String.Format("{0}:{1}", ex.Message, "ExecutePutWebservice"));
    }
}</pre>

<pre>private void PutMessage(string postUrl)
{
    string clientId = "";
    string secret = "";
    string tenantId = "myapplication.onmicrosoft.com";
    string resourceId = "http://app.onmicrosoft.com/myapplication";
 
    var request = (HttpWebRequest)WebRequest.Create(postUrl);
    request.Method = "PUT";
    request.ContentType = "application/xml";
    request.ContentLength = 0;
 
    //Create and Add OAUTH token to header with ADAL
    AddAuthorizationHeader(request, tenantId, clientId, secret, resourceId);
 
    using (var webresponse = request.GetResponse())
    {
        if (webresponse.GetResponseStream() == Stream.Null)
        {
            throw new Exception("Response stream is empty");
        }
 
        var response = (HttpWebResponse)webresponse;
 
        if (response.StatusCode != HttpStatusCode.OK)
        {
            string returnString = response.StatusCode.ToString();
        }
        else
        {
            string returnString = response.StatusCode.ToString();
        }
    }
}</pre>
 

AddAuthorizationHeader

<pre>private void AddAuthorizationHeader(WebRequest client, string tenantId, string clientId, string clientSecret, string resourceId)
{
    client.PreAuthenticate = true;
 
    string authority = string.Format("https://login.microsoftonline.com/{0}",
    tenantId);
    AuthenticationContext authContext = new AuthenticationContext(authority);
    ClientCredential clientCredential = new ClientCredential(clientId,
    clientSecret);
 
    AuthenticationResult authResult = null;
    authResult = authContext.AcquireToken(resourceId,
    clientCredential);
 
    client.Headers.Add("Authorization",
    new AuthenticationHeaderValue("Bearer",
    authResult.AccessToken).ToString());
}</pre>

If you were to ILMerge this plugin with ADAL and deploy the plugin in partial trust, you would have no problem executing it. When changing the plugin from partial to full trust and then executing the plugin you will be faced with the following:

The type initializer for ‘Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext’ threw an exception

ADALError

The Solution

Instead of using the ADAL library to authenticate the caller and creating the token with OAUTH, we can achieve the same by executing a request to the Azure AD tenant specific endpoint.

Microsoft has documented the service to service call authentication scenario here https://msdn.microsoft.com/en-us/library/azure/dn645543.aspx

In summary, we will be creating a POST to https://login.microsoftonline.com/<tenant id>/oauth2/token endpoint and parsing the following parameters to the endpoint:

Azure1

In return we will be given the access token along with the token type, expiry and resource URL in the form of JSON which would look similar to this:

Azure2

So if we go back to the PutMessage method in our Plugin, we are going to replace the AddAuthorizationHeader method which uses the ADAL classes with a method named CreateOAuthAuthorizationToken which uses the service to service call authentication method described above to create the access token manually.

<pre>private void PutMessage(string postUrl)
     {
 
         string clientId = "";
         string secret = "HpYmx+Dg3L1VZwgpQQk/5FyQh8Q2H9d5c0Gwqivp1Gk=";
         string tenantId = "app.onmicrosoft.com";
         string resourceId = "http://app.onmicrosoft.com/mywebservice";
 
         var request = (HttpWebRequest)WebRequest.Create(postUrl);
         request.Method = "PUT";
         request.ContentType = "application/xml";
         request.ContentLength = 0;
 
         //Manually create the auth token
         Task token = CreateOAuthAuthorizationToken(
         clientId,
         secret,
         resourceId,
         tenantId);
         //Add token to the header
         request.PreAuthenticate = true;
         request.Headers.Add("Authorization", string.Format("{0} {1}", "Bearer", token.Result.access_token));
 
         //AddAuthorizationHeader(request, tenantId, clientId, secret, resourceId);
 
         using (var webresponse = request.GetResponse())
         {
             if (webresponse.GetResponseStream() == Stream.Null)
             {
                 throw new Exception("Response stream is empty");
             }
 
             var response = (HttpWebResponse)webresponse;
 
             if (response.StatusCode != HttpStatusCode.OK)
             {
                 string returnString = response.StatusCode.ToString();
             }
             else
             {
                 string returnString = response.StatusCode.ToString();
             }
         }         
     }</pre>
 

CreateOAuthAuthorizationToken

All we are doing here is retrieving the auth token from Azure with the requires authorization parameters and them de-serializing the JSON into a custom object name AzureAccessToken. Take note that I am using Uri.EscapeDataString to encode the strings because using HttpUtlility.HtmlEncode will cause an exception running in full trust.

<pre>public async static Task CreateOAuthAuthorizationToken(string clientId, string clientSecret, string resourceId, string tenantId)
      {
          AzureAccessToken token = null;
          string oauthUrl = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenantId);
          string reqBody = string.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&resource={2}", Uri.EscapeDataString(clientId), Uri.EscapeDataString(clientSecret), Uri.EscapeDataString(resourceId));
 
          HttpClient client = new HttpClient();
          HttpContent content = new StringContent(reqBody);
          content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
          using (HttpResponseMessage response = await client.PostAsync(oauthUrl, content))
          {
              if (response.IsSuccessStatusCode)
              {
                  DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(AzureAccessToken));
                  Stream json = await response.Content.ReadAsStreamAsync();
                  token = (AzureAccessToken)serializer.ReadObject(json);
              }
          }
          return token;
      }
 
      [DataContract]
      public class AzureAccessToken
      {
          [DataMember]
          public string access_token { get; set; }
          [DataMember]
          public string token_type { get; set; }
          [DataMember]
          public string expires_in { get; set; }
          [DataMember]
          public string expires_on { get; set; }
          [DataMember]
          public string resource { get; set; }
      }</pre>
 

At the end, we simply set the Authorization header in the request to the newly retrieved token in the access_token property as follows:

         //Add token to the header
         request.PreAuthenticate = true;
         request.Headers.Add("Authorization", string.Format("{0} {1}", "Bearer", token.Result.access_token));

Now we can register this plugin in full trust mode with no issues at all. Good Luck!

Please follow and like us:
0

7 thoughts on “Alternative to Azure Authentication with ADAL in Dynamics CRM Online Sandbox

  1. Hi,

    Nice Post.

    I need help to understand whether is it possible to authenticate external web service using OAuth within CRM plugin sandbox mode? Please advise.

    In our scenario, we will have to get the token from Azure and pass it to service call. Please help.

    Regards,
    Suraj

  2. Thanks, great article! I have tried this in my plugin I am able to get the token, however, when I try to make the call to get the data from my azure hosted web service I get a 401 unauthorized.

    I have also tried the code in a local console app to see it I could get it working. It has the same issue.

    Have you run into this issue before? I think I might be missing something.

    Thanks

    1. I managed to fix this issue by using a WebClient instead of an HttpClient. Possibly something was wrong in the request. It is working now.

  3. The clientId and secret key are from the CRM OL instance, right? Where do you get them from? In my Azure AD tenant list of apps for “Dynamics CRM Online” I can only see the dashboard, but no configuration options. I am using the trial version.

Leave a Reply

Your email address will not be published. Required fields are marked *