Service to Service authentication in AX7
Starting AX7 Update2 (also included into FallUpdate CTP2) AX supports Service to Service authentication by leveraging OAUTH 2's Client Credentials Grant through Azure Active Directory (AAD). Fundamental details can be found in Daemon or Server Application to Web API . The feature is supported for web services exposed by AX and as a result enables creating unattended services capable of communicating directly with AX without requiring a user in front of a screen to type credentials.
In this article I will show how to set up the feature and use it in a scenario when a client application needs to communicate to Retail Transaction Service (RTS).
1. Let's create an AAD Application which will correspond to your client application:
a) Navigate to https://manage.windowsazure.com (don't use the new Azure Portal while following this article because, atthe time of this writing, the AAD management functionality is in preview there and as a result some features required by AX are not supported there yet) then click on ACTIVE DIRECTORY and select a row representing a directory you want to modify. By default Azure Subscription comes with just one directory so you will most likely see just one row there unless you explicitly created additional directories. So, click on the cell corresponding to the column NAME and you will be redirected to the page showing the directory's details.
b) Click APPLICATIONS->Add->Add an application my organization is developing
In the field NAME, type any name you want, for instance RtsClient
Keep default Type (Web) and then click Next button
Type any Uri you want into the fields SIGN-ON URL and APP ID URI, for instance: http://RtsClient
Those 2 values doesn't actually matter for this article's purpose.
Click Complete button to create the application.
c) Click Configure tab and scroll down to the keys section. Expand the combobox and select proper expiration for the key. This is the application's password which will be used to acquire Access Token. The value of the password will not be seen at this time, that is normal, proceed with the rest of the steps below.
d) Scroll to the very bottom of the page to the section Permissions to other applications and then click Add Application button.
In the list of applications find Microsoft Dynamics ERP and click on that row so the application's name appears in the SELECTED section:
Then click Complete button at the bottom right corner, as a result you will be navigated back to the Configure tab
e) the section Permissions to other applications will now contain a row corresponding to Dynamics Erp (AX). Expand the combobox Application Permissions and mark the checkbox:
The purpose of steps (d) and (e) to declare that our newly created AAD application requires a permission to access AX AAD application. This, upon successfully issued token, will result in a proper set of claims inside security token, the claims will eventually be used in AX while validating the token.
f) Click Save button at the bottom of the page. Once the application's configuration is saved you will be able to see the value of the key (password), copy it and store in some safe location, once you navigate back from this page you will never be able to see the value of that key again. Also, while on this tab note the value in the CLIENT ID field, you will need it later.
This completes the application setup in AAD.
2. Just created application needs to be whitelisted in AX, this will result in successful authentication for the requests carrying the Access Token.
a) in AX go to System Administration->Setup->Azure Active directory applications. If you are using Retail SKU then navigate to Retail -> Headquarters setup -> Azure Active Directory applications. Or, just type Azure Active Directory applications in the Search Box in AX which can be found near top right corner and you will locate the UI regadless of your SKU.
b) Click New and paste the value of the CLIENT ID into the field Client Id. Provide any descriptive name of your app in the Name field. Once you click the field User ID you will see a list of users to choose from, select RetailServerAccount there. The user you select will define, via its Roles, what your application can and cannot do. You can add as many applications here as you need.
This completes setup in AX.
3. Finally let's create simple application which will leverage RTS to search for customers by using keyword Contoso. While doing that we will leverage several Retail and ADAL libraries which help communicating to AAD to get the token and then send a request to RTS and process its response.
a) Run Visual Studio and create new Console Project (.NET)
b) Add references to the following DLLs:
J:\RetailSDK\References\Microsoft.Dynamics.Retail.Cdx.RealtimeServiceClientLibrary.dll
J:\RetailSDK\References\Microsoft.Dynamics.Retail.Security.dll
J:\RetailSDK\References\Microsoft.IdentityModel.Clients.ActiveDirectory\2.14.0.0\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
Microsoft.Dynamics.Retail.Diagnostics
System.Configuration.dll
System.ServiceModel.dll
Microsoft.IdentityModel.dll
c) Replace content of the file Program.cs with the following:
using Microsoft.Dynamics.Retail.Cdx.RealtimeServiceClientLibrary; using Microsoft.Dynamics.Retail.Security; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Configuration; using System.ServiceModel; class Program { static void Main() { RealtimeServiceClient client = GetRtsClient(); var response = client.InvokeMethod("searchCustomers", new object[] { "Contoso" }); foreach (object obj in response.Data) { Console.WriteLine(obj); } } static RealtimeServiceClient GetRtsClient() { BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport); // !!!! Replace with safer approach to avoid explicitly dealing with unecnrypted password !!! string unprotectedPassword = ConfigurationManager.AppSettings["AadClientSecret"]; string protectedPassword = DPAPICipher.Protect(DPAPICipher.ConvertToSecureString(unprotectedPassword)); // !!!! ReadOnlyCollection<string> clientSecrets = new List<string> { protectedPassword}.AsReadOnly(); ServiceToServiceAuthenticationConfiguration authConfig = new ServiceToServiceAuthenticationConfiguration( ConfigurationManager.AppSettings["AadAuthority"], ConfigurationManager.AppSettings["AadServerResourceId"], ConfigurationManager.AppSettings["AadClientId"], clientSecrets); RealtimeServiceConfiguration config = new RealtimeServiceConfiguration( RealtimeServiceAuthType.ServiceToServiceClientSecretAuthentication, authConfig, ConfigurationManager.AppSettings["RTSRelativeAddress"], ConfigurationManager.AppSettings["AosUrl"], binding, string.Empty, ConfigurationManager.AppSettings["Company"], ConfigurationManager.AppSettings["Language"]); return new RealtimeServiceClient(config); } }
The code creates an instance of BasicHttpBinding, then reading configuration parameters from the config file and then creates an instance of ReailtimeServiceClient which is then used to make actual calls.
To keep the code in this article short and simple I doesn't demonstrate best practice while dealing with the Key (password) - the password is stored in unencrypted form in the configuration file, it is then encrypted in the code above (see a call to DPAPICipher.Protect) and eventually used in constructor of RealtimeServiceClient (that object requires an encrypted password). In your real applications you should have a dedicated application/process which will first encrypt (you can leverage the Protect method above) the password acquired from AAD and only then stored in the configurationfile. This way, even if the key is stolen an attacker will not be able to use/decrypt it on a different machine. So, do not save your secrets unprotected.
Replace the content of your App.config with the one below:
<configuration> <appSettings> <add key="AadAuthority" value="https://login.windows.net/<YourTenantSpecificPrefixIsHere>.onmicrosoft.com"/> <add key="AadServerResourceId" value="https://<YourDeploymentSpecificPrefixIsHere>.cloudax.dynamics.com"/> <add key="AadClientId" value="<YourClientIdIsHere>"/> <add key="AadClientSecret" value="<YourClientSecretIsHere>"/> <add key="AosUrl" value="<YourDeploymentSpecificValueIsHere>"/> <add key="Company" value="USRT"/> <add key="Language" value="en-US"/> <add key="RTSRelativeAddress" value="RetailCDXRealTimeService" /> </appSettings> </configuration>
Don't forget to replace number of values with your deployment specific values:
- for AadAuthority replace <YourTenantSpecificPrefixIsHere> with the value corresponding to your tenant, you can find out its value by, for instance, logging into https://manage.windowsazure.com and then looking into the address bar for the Url, it will be something like this:
The value you need for your environment is filled with black rectangle in the screenshot above.
- for both - AadServerResourceId and AosUrl specify your AOS Url.
Other values are self descriptive.
When you run the app you should see customer's search result (in XML form) in your Console window which means that your application successfully passed authentication with AX.
*This post is locked for comments