<img alt="" src="https://secure.intelligent-consortium.com/791393.png" style="display:none;">

Are You Having Trouble Getting an OAuth2.0 Access Token From AL?

In this blog, I discuss retrieving an OAuth2.0 access token from a Business Central AL extension.

Let’s Get Started

Create an App Registration in Azure Active Directory

Get started by creating your App Registration in the Azure Active Directory. Be sure to set up the Azure AD application in Business Central afterward with the necessary permissions sets.

You can find documentation from Microsoft here.

Create an AL extension

You’ll be adding code to an AL extension. In case you haven’t built one before, I wanted to supply a link to the documentation.

Now Let’s Code!

Okay, I’m writing this on 11/16/22. Why does that matter? Well, I’ll assume you’ve already been browsing for a solution, and you’ve already tried out a few things written on other blogs, spent hours and hours, AND YOU HAVEN’T GOTTEN IT TO WORK!

That’s what I went through anyway. And we know the Business Central framework changes, and what worked two years ago might not work anymore.

You’ll find the complete procedure that’s working for me below, but first, I’ll point out the key details that were missing from the examples I tried previously.

Don’t use a JSON node in your content

I found a few examples sending JSON in the Content of the HttpRequestMessage. That didn’t work, but this did. I used a Label variable and defined it like this:

ContentLbl: Label 'grant_type=%1&scope=%2&client_id=%3&client_secret=%4', Comment = '%1 is grant_type, %2 is scope, %3 is the client id, %4 is the client secret';

URL Encoding

Your Content-Type needs to be application/x-www-form-urlencoded.

                      HttpHeaders.Add('Content-Type', 'application/x-www-form-urlencoded');

Also, you will need to URL Encode the variables you’re assigning to the ContentLbl string. Use the URLEncode procedure in Type Helper codeunit to encode the variables in your string.

Remove the opening and closing brackets from your guids

You might be using a setup table to send variables to your function that stores the Tenant ID and Client ID as guids. If so, remove the brackets when creating your URLs and content, so they don’t end up in your strings.

                   TenantId := DelChr(INVCWebhookSubscription."Tenant Id", '=', '{}');

Remove the quotes from the Access Token

After connecting successfully and retrieving a non-error response, you can grab your token. I stuff the response into a JsonObject. If you do that, you can retrieve the “access_token” node. However, the value comes back with surrounding quotes. Remove these quotes.

                   AccessToken := DelChr(AccessToken, '<>', '"');

Full Procedure

Here’s a sample procedure in full (The INVCWebhookSubscription is a custom table I’m using to store my credential variables related to a Business Central Webhook Subscription):

local procedure GetAccessToken(var INVCWebhookSubscription: Record "INVC Webhook Subscription") AccessToken: Text
   var
                   TypeHelper: Codeunit "Type Helper";
                   HttpResponseMessage: HttpResponseMessage;
                   HttpRequestMessage: HttpRequestMessage;
                   HttpClient: HttpClient;
                   HttpContent: HttpContent;
                   HttpHeaders: HttpHeaders;
                   JsonObject: JsonObject;
                   JsonToken: JsonToken;
                   JsonValue: JsonValue;
                   ContentText: Text;
                   ResponseText: Text;
                   GrantType: Text;
                   Scope: Text;
                   TenantId: Text;
                   ClientId: Text;
                   ClientSecret: Text;
                   ContentJsonLbl: Label 'grant_type=%1&scope=%2&client_id=%3&client_secret=%4', Comment =
'%1 is grant_type, %2 is scope, %3 is the client id, %4 is the client secret';

                   URL: Text;
                   URLLbl: Label 'https://login.microsoftonline.com/%1/oauth2/v2.0/token', Comment = '%1 is the tenant id';
                   HttpErrorCodeErr: Label '%1. Error Status Code: %2', Comment = '%1 is error. %2 is status code';
                   AccessTokenErr: Label 'Cannot get Access Token.';
   begin
                   //set url
                   TenantId := DelChr(INVCWebhookSubscription."Tenant Id", '=', '{}');

                   URL := StrSubstNo(URLLbl, TenantId);
                   //set content body
                   GrantType := 'client_credentials';
                   Scope := 'https://api.businesscentral.dynamics.com/.default';
                   ClientId := DelChr(INVCWebhookSubscription."Client Id", '=', '{}');
                   ClientSecret := INVCWebhookSubscription.” Client Secret”;
                   ContentText := StrSubstNo(ContentJsonLbl, TypeHelper.UrlEncode(GrantType), TypeHelper.UrlEncode(Scope), TypeHelper.UrlEncode(ClientId), TypeHelper.UrlEncode(ClientSecret));
                   HttpContent.WriteFrom(ContentText);

                   // Retrieve the contentHeaders associated with the content
                   HttpContent.GetHeaders(HttpHeaders);
                   HttpHeaders.Clear();
                   HttpHeaders.Add('Content-Type', 'application/x-www-form-urlencoded');

                   // Assign content to request.Content
                   HttpRequestMessage.Content := HttpContent;

                   HttpRequestMessage.SetRequestUri(URL);
                   HttpRequestMessage.Method := 'POST';

                   HttpClient.Send(HttpRequestMessage, HttpResponseMessage);
                   if not HttpResponseMessage.IsSuccessStatusCode() then
                   error(HttpErrorCodeErr, AccessTokenErr, HttpResponseMessage.HttpStatusCode);

                   // Read the response content as json.
                   HttpResponseMessage.Content().ReadAs(ResponseText);
                   if not JsonObject.ReadFrom(ResponseText) then
                   error(AccessTokenErr);
                   if not JsonObject.Contains('access_token') then
                   error(AccessTokenErr);
                   JsonObject.Get('access_token', JsonToken);
                   JsonValue := JsonToken.AsValue();
                   JsonToken.WriteTo(AccessToken);
                   AccessToken := DelChr(AccessToken, '<>', '"');
                   INVCWebhookSubscription.SetBlobText(AccessToken, INVCWebhookSubscription.FieldNo("Access Token"));
                   INVCWebhookSubscription.Modify();
   end;

Calling the Procedure

In case you’re not familiar with using the token, I’m providing another sample procedure that calls the procedure above and uses the token. (As a bonus, you can glean now to subscribe to a Business Central Webhook in this procedure.):

procedure PostPatchSubscription(var INVCWebhookSubscription: Record "INVC Webhook Subscription");
   var
                   HttpClient: HttpClient;
                   HttpRequestMessage: HttpRequestMessage;
                   HttpResponseMessage: HttpResponseMessage;
                   HttpHeaders: HttpHeaders;
                   HttpRequestHeaders: HttpHeaders;
                   HttpContent: HttpContent;
                   JsonObject: JsonObject;
                   JsonToken: JsonToken;
                   JsonValue: JsonValue;
                   SubscriptionId: Text;
                   RequestText: Text;
                   RequestUrl: Text;
                   ResponseText: Text;
                   RequestUrlLbl: Label '%1/%2/%3', Comment = '%1 is the base url, %2 is the environment name, %3 is the Subscription url.';
                   SubscriptionLbl: Label '%1(''%2'')', Comment = '%1 is url, %2 is subscriptionId';
                   ResponseMsg: Label 'Url: %1; Method: %2; Status Code: %3; Reason Phrase: %4; Response: %5', Comment = '%1 url, %2 Method, %3 - Status, %4 = Phrase, %5 is response text.';
   begin
                   INVCWebhookSubscription.TestField("Notification Url");
                   INVCWebhookSubscription.TestField("Resource Url");

                   RequestText := INVCWebhookSubscription.GetSubscriptionRequestJsonText();

                   INVCWebhookSubscription.SetBlobText(RequestText, INVCWebhookSubscription.FieldNo("Subscription Request"));
                   INVCWebhookSubscription.SetBlobText('', INVCWebhookSubscription.FieldNo("Subscription Response"));
                   INVCWebhookSubscription.Modify();
                   Commit();
                   INVCWebhookSubscription.Get(INVCWebhookSubscription.Code);
                   // Add the payload to the content
                   HttpContent.WriteFrom(RequestText);

                   // Retrieve the contentHeaders associated with the content
                   HttpContent.GetHeaders(HttpHeaders);
                   HttpHeaders.Clear();
                   HttpHeaders.Add(‘Content-Type’, ‘application/json’);

                   // Assigning content to request.Content will actually create a copy of the content and assign it.
                   // After this line, modifying the content variable or its associated headers will not reflect in
                   // the content associated with the request message
                   HttpRequestMessage.Content := HttpContent;
                   RequestUrl := StrSubstNo(RequestUrlLbl, INVCWebhookSubscription.GetBlobText(INVCWebhookSubscription.FieldNo(“Base Url”)), INVCWebhookSubscription.” Environment Name”, INVCWebhookSubscription.GetBlobText(INVCWebhookSubscription.FieldNo("Subscription Url")));
                    if INVCWebhookSubscription.” Subscription Id" = '' then
                       HttpRequestMessage.Method := 'POST'
                   else begin
                       HttpRequestMessage.GetHeaders(HttpRequestHeaders);
                       HttpRequestHeaders.Add('If-Match', '*');
                       HttpRequestMessage.Method := 'PATCH';
                       RequestUrl := StrSubstNo(SubscriptionLbl, RequestUrl, INVCWebhookSubscription."Subscription Id");
                   end;
                   HttpRequestMessage.SetRequestUri(RequestUrl);

                   HttpClient.DefaultRequestHeaders.Add('Authorization', 'Bearer ' + GetAccessToken(INVCWebhookSubscription));
                   HttpClient.Send(HttpRequestMessage, HttpResponseMessage);
                   if (HttpResponseMessage.HttpStatusCode = 404) and (HttpRequestMessage.Method = 'PATCH') then begin
                       INVCWebhookSubscription.” Subscription Id" := '';
                       INVCWebhookSubscription.Modify();
                       Commit();
                       INVCWebhookSubscription.Get(INVCWebhookSubscription.Code);
                       INVCWebhookSubscription.SetRecFilter();
                       PostPatchSubscription(INVCWebhookSubscription);
                       exit;
                   end;
                   // Read the response content as json.
                   HttpResponseMessage.Content().ReadAs(ResponseText);
                   if JsonObject.ReadFrom(ResponseText) then begin
                       if JsonObject.Contains(‘expirationDateTime’) then begin
                            JsonObject.Get('expirationDateTime', JsonToken);
                            JsonValue := JsonToken.AsValue();
                            INVCWebhookSubscription.” Expiration Date Time”:= JsonValue.AsDateTime();
                       end;
                       if INVCWebhookSubscription.” Subscription Id" = '' then
                            if JsonObject.Contains(‘subscriptionId’) then begin
                                JsonObject.Get('subscriptionId', JsonToken);
                                JsonToken.WriteTo(SubscriptionId);
                                INVCWebhookSubscription.” Subscription Id" := CopyStr(DelChr(SubscriptionId, '=', '"'), 1, MaxStrLen(INVCWebhookSubscription."Subscription Id"));
                            end;
                   end;
                   INVCWebhookSubscription.SetBlobText(StrSubstNo(ResponseMsg, HttpRequestMessage.GetRequestUri(), HttpRequestMessage.Method, HttpResponseMessage.HttpStatusCode, HttpResponseMessage.ReasonPhrase, ResponseText), INVCWebhookSubscription.FieldNo("Subscription Response"));
                   INVCWebhookSubscription.Modify();
   end;

And there you have it. That is how you can get an OAuth2.0 access token in Business Central. I hope I was able to help cut through some of the confusion out there for you and get you to where you need to be. If you have any questions, contact me by clicking the button below. Thank you, and happy coding.

Contact Ross Bottorf

Business Central API's changed from Basic to OAuth2.0? Learn how this affects service-to-service calls.
Read: Calling Rapid Start Automation APIs using OAuth2.0 Authentication

 

New call-to-action

Ross Bottorf

Ross Bottorf

Ross Bottorf works as a Development Consultant at Innovia. Ross has over 10 years of experience as a Dynamics BC/NAV Developer. He graduated from Arizona State University and now resides in Tucson, Arizona, with his wife and son. Ross enjoys playing the guitar and loves music and living in the Southwest.

Related Posts