In Focus

Generate Access Token For Dynamics 365 Single Tenant Server To Server Authentication

In Dynamics 365 integration scenarios, most of the times, we need to authenticate only single tenant. Since now Dynamics 365 authentication through Azure AD only (for online instances) is recommended, let’s see how to do it.

Dynamics 365 authentication is recommended only through Azure AD (for online instances). To achieve this, first of all, we need to create an app in Azure Active Directory and the good news is that you don’t need an Azure subscription to try this out; your free trial of Dynamics 365 is enough.

The following 4 steps are involved in the implementation of this. You can always jump to the next step if you have already implemented (or you know) the current step. This topic may not be very new to everyone, but when I went to implement the same recently, it took me a few hours to make it work, perhaps because of the recent frequent Azure updates or the outdated content. This led me to write this.
  1. Create and configure the app in Azure Active Directory.
  2. Create a user in Azure AD and configure it as an application user in Dynamics 365
  3. Write C# code with ADAL (Active Directory Authentication Library) to generate the Access Token
  4. Make requests to Dynamics 365 with the above-generated Access Token

Step 1 - Create Azure AD App

Navigate to Azure (https://portal.azure.com).
 
On the left menu, click on Azure Active Directory -> App registrations (Preview) => + New registration. We will use the Preview version only because it has more straight-forward options and going forward, this only is going to be the default option.

create-azure-ad-app
 
Give some name to your app, and for account type, select "Accounts in this organizational directory only" because we need it for a single tenant only.

single-tenant-app

Once the app is created, click on "API permissions" to add a new permission to your app.

adding-new-permissions
 
Select Dynamics CRM here.

  dynamics-crm-permission
 
Check the user_impersonation box on the upcoming screen and click "Add permissions".

user-impersonation
 
These permissions require admin's consent. Click on the "Grant admin consent for D365In" button and confirm it. You need the administrator role to do it.

grant-admin-consent

Now, your app is created. We need to use three things:

  1. Application Id, aka, Client Id
  2. Tenant Id
  3. Client Secret
The Application Id and Tenant Id can be grabbed from the "App Overview".

 app-overview

To generate the client secret, go to "Certificates & secrets" and then "+ New client secret". Give some description and select the validity of your secret. Then, click "Add".

  client-secret
 
Client Secret is something that you should keep secret; that is why you can see this only once after generation. Copy it and keep it safe to use later.

generated-secret
 
Step 2: Create Application User
  • You need to create a new user. All CRM API calls will be made on behalf of this user.
  • This user does not require a Dynamics 365 license.
  • This user should be created from Azure (https://portal.azure.com), not from Office Portal (https://admin.microsoft.com)
  • Navigate to Azure -> Azure Active Directory -> Users and click on "+New user".

new-app-user

 

  • Here, the username field must have the same domain name as your organization.
  • Once this user is created, go to your Dynamics 365 instance.
  • Navigate to Dynamics 365 -> Settings -> Security; click on "Users" here.
  • Change the view to "Application Users" and click on "+ NEW" to create a new application user. 

    set-view-as-application-user
  • You may need to set the form also as an Application User if it’s not coming by default.

    set-form-as-application-user

     

  • Here, the Application ID must be the same as Azure AD App created in the previous step. You can keep the username and email same as the one created in Azure AD. Though it’s not necessary to be the same, I have tried with the different name also. Once you save it, the Application ID URI & Azure AD Object ID will auto-populate.

    new-app-user-d365

     

  • Now, you need to assign a security role to this user to perform an operation on desired records. I’ve seen in many blogs that this user must have a custom security role; so you can copy some existing role and assign it. But when I tried with OOB security role, it was still working.

Step 3: Get Access Token with ADAL

Create a new project in Visual Studio and add the ADAL package via NuGet.

ADAL-NuGet.png
 
Create the below-shown method and replace the Application Id, Client Secret, Tenant Id, and your organization's URL at appropriate places.
  1. /// Required-Namespaces  
  2. using Microsoft.IdentityModel.Clients.ActiveDirectory;  
  3. using System.Threading.Tasks;  
  4.   
  5. /// Method-to-generate-Access-Token  
  6. public static async Task<string> AccessTokenGenerator()  
  7. {  
  8.     string clientId = "Azure AD App Id";  
  9.     string clientSecret = "Client Secret Generated for App";  
  10.     string authority = "https://login.microsoftonline.com/< your app tenant guid >";  
  11.     string resourceUrl = "https://< your D365 org>.< crm instance location e.g crm, crm8 >.dynamics.com"; // Org URL  
  12.   
  13.     ClientCredential credentials = new ClientCredential(clientId, clientSecret);  
  14.     var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);  
  15.     var result = await authContext.AcquireTokenAsync(resourceUrl, credentials);  
  16.     return result.AccessToken;  
  17. }  
Step 4: Consuming Access Token
 
You have the access token. Now, you can make a request to your Dynamics 365 by including this to your HTTP requests, as shown in the below method.
  1. public static async Task<HttpResponseMessage> CrmRequest(HttpMethod httpMethod, string requestUri, string body = null)  
  2. {  
  3.     var accessToken = await AccessTokenGenerator();  
  4.     var client = new HttpClient();  
  5.     var msg = new HttpRequestMessage(httpMethod, requestUri);  
  6.     msg.Headers.Add("OData-MaxVersion""4.0");  
  7.     msg.Headers.Add("OData-Version""4.0");  
  8.     msg.Headers.Add("Prefer""odata.include-annotations=\"*\"");  
  9.   
  10.     // Passing AccessToken in Authentication header  
  11.     msg.Headers.Add("Authentication", $"Bearer {accessToken}");  
  12.   
  13.     if (body != null)  
  14.         msg.Content = new StringContent(body, UnicodeEncoding.UTF8, "application/json");  
  15.   
  16.     return await client.SendAsync(msg);  
  17. }  
You can make different requests using the above method. Here is an example for retrieving all contacts with GET.
  1. var contacts = CrmRequest(  
  2.     HttpMethod.Get,   
  3.     "https://efrig.api.crm8.dynamics.com/api/data/v9.1/contacts")  
  4.     .Result.Content.ReadAsStringAsync();  

Full Code (Replace your Azure credentials before executing)

  1. using Microsoft.IdentityModel.Clients.ActiveDirectory;  
  2. using System.Net.Http;  
  3. using System.Text;  
  4. using System.Threading.Tasks;  
  5.   
  6. namespace D365S2S  
  7. {  
  8.     class Program  
  9.     {  
  10.         static void Main(string[] args)  
  11.         {  
  12.             var contacts = CrmRequest(  
  13.                 HttpMethod.Get,  
  14.                 "https://efrig.api.crm8.dynamics.com/api/data/v9.1/contacts")  
  15.                 .Result.Content.ReadAsStringAsync();  
  16.             // Similarly you can make POST, PATCH & DELETE requests  
  17.         }  
  18.   
  19.         public static async Task<string> AccessTokenGenerator()  
  20.         {  
  21.             string clientId = "13950f0e-0000-4e2f-0000-b923302c4338"// Your Azure AD Application ID  
  22.             string clientSecret = "0^C#%0000DR7/#Z[-.m5aYO00000000$"// Client secret generated in your App  
  23.             string authority = "https://login.microsoftonline.com/ceb48f70-0000-1111-0000-9170f6a706a6"; // Azure AD App Tenant ID  
  24.             string resourceUrl = "https://efrig.crm8.dynamics.com"; // Your Dynamics 365 Organization URL  
  25.   
  26.             var credentials = new ClientCredential(clientId, clientSecret);  
  27.             var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);  
  28.             var result = await authContext.AcquireTokenAsync(resourceUrl, credentials);  
  29.             return result.AccessToken;  
  30.         }  
  31.   
  32.         public static async Task<HttpResponseMessage> CrmRequest(HttpMethod httpMethod, string requestUri, string body = null)  
  33.         {  
  34.             // Acquiring Access Token  
  35.             var accessToken = await AccessTokenGenerator();  
  36.   
  37.             var client = new HttpClient();  
  38.             var message = new HttpRequestMessage(httpMethod, requestUri);  
  39.   
  40.             // OData related headers  
  41.             message.Headers.Add("OData-MaxVersion""4.0");  
  42.             message.Headers.Add("OData-Version""4.0");  
  43.             message.Headers.Add("Prefer""odata.include-annotations=\"*\"");  
  44.   
  45.             // Passing AccessToken in Authentication header  
  46.             message.Headers.Add("Authorization", $"Bearer {accessToken}");  
  47.               
  48.             // Adding body content in HTTP request   
  49.             if (body != null)  
  50.                 message.Content = new StringContent(body, UnicodeEncoding.UTF8, "application/json");  
  51.   
  52.             return await client.SendAsync(message);  
  53.         }  
  54.     }  
  55. }  

I hope it helps. Feel free to get in touch for any query or suggestion.