General

We had requirement to integrate with Microsoft Dynamics CRM Online in one of our projects. Dynamics CRM is using Azure AD for authentication.

Natural decision was to use Dynamics REST API since Web Services interface is older and will become obsolete sooner more than later. But before REST API could be used, we need to authenticate with Azure AD to get token for REST API calls.

Biggest challenge was the fact that we needed to do integration from server process running in Azure without user logged in or any kind of user interface at all. It's pretty straightforward to integrate with Azure AD if you have Azure Web App, and user credentials can be asked from user when he or she is using the application. Or even using SSO if user is already logged in into Azure AD.

Customer gave us Azure AD account details (username and password) to be used when accessing Dynamics CRM.

Adding application to Azure AD

Follow these steps to add your application to Azure AD and give required permissions to it:

  1. Login to portal.azure.com
  2. Click "Azure Active Directory" on left menu

Step 2

  1. Click "App registrations"

Step 3

  1. Click "Add"

Step 4

  1. Fill "Name" and "Sign-on URL", leave "Application Type" to default value (Web app / API), and click "Create"

Step 5

  1. After Azure has created new Application registration, select it from list

Step 6

  1. Application ID (Client ID in OAuth2 terms) is shown in main window

Step 7

  1. Create "Client Secret" by selecting "Keys" in Settings menu

Step 8

  1. Fill "Key description", select key "Duration" from dropdown list and Click "Save" (Key is shown after Save has been pressed)

Step 9

NOTE: Copy the key immediately, there is no way to get it after this window is closed!

  1. Final step is to give application permissions to Access CRM Online as organization users. Click "Required permissions" in settings menu and click "Add"

Step 10

  1. Next "Click Select an API", select "Dynamics CRM Online (Microsoft.CRM) and click "Select"

Step 11

  1. Select "Access CRM Online as organization users" and click "Select"

Step 12

  1. Finally click "Done"

Step 13

Authentication

ADAL is Microsoft Azure Active Directory Authentication Libraries, available for all popular platforms including .NET, NodeJS, JavaScript and more.

We tried number of methods in ADAL 2.x and ADAL 3.x versions to get token from Azure AD. We even managed to get our application authenticated with Azure AD, but when we tried to access Dynamics CRM with REST API, we always got 401 Unauthorized as response.

Finally we decided to throw ADAL away, and tried to do authentication with using OAuth2 on top of pure HTTPS.

To authenticate we need the following parameters:

namespace Security.Authentication
{
  public class AadPasswordGrantAuthenticationServiceSettings
  {
    public string CliendId { get; set; }
    public string ClientSecret { get; set; }
    public string Resource { get; set; }
    public string User { get; set; }
    public string Password { get; set; }
    public string Tenant { get; set; }

    public AadPasswordGrantAuthenticationServiceSettings()
    {
      CliendId = "f0b6f6ad-4a38-4a27-a01d-650c78e49afd";
      ClientSecret = "IR9bhMuzpRoElBmaen10XQUeXqpDGYnLVr3YEMIxJzY=";
      Resource = "https://customercrm.crm.dynamics.com/";
      User = "integration.crm@customerdomain.com";
      Password = "50meveryCrypticPassw0@dInHere_#55%ยค";
      Tenant = "customertenant.onmicrosoft.com";
    }
  }
}

Where

We also need to define Token that is returned from Azure AD in case of successful authentication:

namespace Security.Authentication
{
  public class Token
  {
    public string token_type { get; set; }
    public string scope { get; set; }
    public int expires_in { get; set; }
    public int expires_on { get; set; }
    public int not_before { get; set; }
    public string resource { get; set; }
    public string access_token { get; set; }
    public string refresh_token { get; set; }
    public string id_token { get; set; }
  }
}

Finally we created a class that can be used to acquire tokens from Azure AD:

using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Security.Authentication
{
  public class AadPasswordGrantAuthenticationService
  {
    private readonly ILogger _logger;
    private readonly string _url;
    private readonly List<KeyValuePair<string, string>> 
      _requestParams;

    public AadPasswordGrantAuthenticationService(
        AadPasswordGrantAuthenticationServiceSettings settings)
    {
      _requestParams = new List<KeyValuePair<string, string>>
      {
        new KeyValuePair<string, string>(
            "client_id", settings.CliendId),
        new KeyValuePair<string, string>(
            "client_secret", settings.ClientSecret),
        new KeyValuePair<string, string>(
            "resource", settings.Resource),
        new KeyValuePair<string, string>(
            "grant_type", "password"),
        new KeyValuePair<string, string>(
            "username", settings.User),
        new KeyValuePair<string, string>(
            "password", settings.Password)
      };

      _url = string.Format(
        "https://login.windows.net/{0}/oauth2/token", 
          settings.Tenant);
    }

    public async Task<Token> AcquireTokenAsync()
    {
      using (var httpClient = new System.Net.Http.HttpClient())
      {
        httpClient.DefaultRequestHeaders.Add(
            "Cache-Control", "no-cache");

        using (var httpContent = 
          new FormUrlEncodedContent(_requestParams))
        {
          using (var response = await httpClient
            .PostAsync(_url, httpContent))
          {
            if (!response.IsSuccessStatusCode)
            {
              return null;
            }

            return JsonConvert.DeserializeObject<Token>(
              await response.Content.ReadAsStringAsync());
          }
        }
      }
    }
  }
}

That's it!

Using REST API

Now we can use AadPasswordGrantAuthenticationService class to get token before sending request to Dynamics CRM REST API. This is how token is included in REST API calls:

private async Task<string> SendReceiveAsync(HttpMethod httpMethod, 
  string query)
{
  // Renew token before each request
  var token = await _aadPasswordGrantAuthenticationService
    .AcquireTokenAsync();
  if (token == null) { return string.Empty; }

  using (var request = new HttpRequestMessage(httpMethod, query))
  {
    _crmHttpClient.HttpClient.DefaultRequestHeaders.Authorization = 
      new AuthenticationHeaderValue(
        token.token_type, token.access_token);

    using (var response = await _crmHttpClient.HttpClient
      .SendAsync(request))
    {
      if (!response.IsSuccessStatusCode)
      {
        return string.Empty;
      }

      return await response.Content.ReadAsStringAsync();
    }
  }
}

Final words

I'm sure there are other ways to do authentication for Dynamics CRM Online, even somehow with ADAL library. But it's pretty difficult to debug authentication problems, you never know what is wrong in a way you tried to authenticate. I hope solution we came up will help you to use your time in solving real problem, not fighting with authentication.

Sami Ovaska @samiovaska