Tuesday, August 28, 2018

D365FO - C#/X++ - Authentication & Custom web service examples

While starting my path with D365FO and the need to create custom web services to integrate outside systems with D365FO with no previous Azure experience I found myself spending a large amount of time trying to figure out how to properly authenticate with D365FO along with the different options we now have with D365FO compared to the old AX2012 AIF services.

There doesn't seem to be many resources online or in books about this and each one I was able to find seems to be structured differently compared if we are doing interactive logins, defined creds, or using the web api/secret key even though the difference is tiny.

Note: this is just my first stab at authentication so there might be better ways to do some of this. These are just examples that I have found to work well.


There are three different ways to accomplish authenticating with the D365FO system

1. Interactive login
2. Define username/password
3. Using secret key

Because option 2 requires the user the to login interactively before we can use it the examples I am providing will not go over it in my C# app but the code is listed at the bottom of this post.

If you receive the error The user or administrator has not consented to use the application with ID # named #. Send an interactive authorization request for this user and resource

It is because the exact scenario I decided has occurred and you need to login in interactively to grant it permission.

Files:
C# D365Auth - Download - This project includes the following:
The project includes the odataclient.cs/tt which is why it is so big

D365FO Azure Authentication - Interactive & Defined Creds (Secret key), Setting up a class to define multiple environments

SOAP - Calling custom webservice - single input/single output, No input/multiple output, Multiple inputs/multiple outputs, Single input/update record/single output(before and after update results)

JSON - No input/multiple outputs, Single input/single output

Odata - Calls customer entity to get basic customer info, filter customer entity by defined customer account

X++ D365FO - Download - This project includes the following:
Webservice class: method that expects inputclass outputs class, method that expects a string and returns a string, method with no parms and returns an output class, method that inspects 2 parms and returns a string.

It is good to note that I found if you name the service + service group with the same name you may run into issues even though it will compile.



Outside of downloading the project I thought I would post the 2 main methods  I have defined for interactive and secret key login so it is easy to tell the difference.

Interactive:
        public static AuthenticationResult GenerateAzureToken(AXEnvironments _environmentName)
        {
            //create the context of the token
            AuthenticationContext context = new AuthenticationContext(_environmentName.AzureADTenant, TokenCache.DefaultShared);
            
            //create token for the client 
            Task<AuthenticationResult> task = context.AcquireTokenAsync(_environmentName.AzureResource, _environmentName.NativeAzureClientAppId, new Uri(_environmentName.UriString), new PlatformParameters(PromptBehavior.Always));
            
            //wait for the task to finsh
            task.Wait();
            
            //return the result
            return task.Result;
        }

Defined:
        public static AuthenticationResult GenerateAzureToken(AXEnvironments _environment)
        {

            //create the credential object based upon the encrypted secret key in azure
            ClientCredential creds = new ClientCredential(_environment.WebAzureClientAppId, _environment.AzureClientSecret);

            //setup the context of the token
            AuthenticationContext context = new AuthenticationContext(_environment.AzureADTenant, TokenCache.DefaultShared);
            
            //generate the token
            Task<AuthenticationResult> task = context.AcquireTokenAsync(_environment.AzureResource, creds);
            
            //wait for the task to finish
            task.Wait();
            
            //return the result
            return task.Result;
        }


Both of the methods are called via
        public static string GetAzureAuthenticationHeader(AXEnvironments _environment)
        {
            AuthenticationResult result;
            string authHeader = "";

            try
            {
                //generate the token and get an authorization
                result = GenerateAzureToken(_environment);
                authHeader = result.CreateAuthorizationHeader();
            }
            catch
            {
                authHeader = "";
            }

            return authHeader;
        }

Which is passed to
string authHeader = D365Auth.AXAuthorizationDefined.GetAzureAuthenticationHeader(currentEviornment);

which is sent to the http request header.


Just for reference if you do want to use a defined username and password you can use the following

        public static AuthenticationResult GenerateAzureToken(AXEnvironments _environment)
        {

            //create the credential object based upon the stored username/password
            UserPasswordCredential credential = new UserPasswordCredential(_environment.UserName, _environment.Password);
            
            //setup the context of the token
            AuthenticationContext context = new AuthenticationContext(_environment.AzureADTenant, TokenCache.DefaultShared);
            
            //generate the token
            Task<AuthenticationResult> task = context.AcquireTokenAsync(_environment.AzureResource, _environment.AzureClientAppId, credential);
            
            //wait for the task to finish
            task.Wait();
            
            //return the result
            return task.Result;
        }




This post assumes you have already setup authenication in Azure and D365FO which is gone over @ D365FO - Azure authentication setup for web services (defined creds and interactive login)






No comments:

Post a Comment