
Because I got the question if it was possible to use the client credentials flow in Business Central I decided to write a quick blog post about that as well. Codeunit OAuth2 provides a number of functions to acquire access tokens with different authorization flows, including the client credentials flow. The function AcquireTokenWithClientCredentials can be used for this purpose. Read more about the OAuth2 module here: https://github.com/microsoft/ALAppExtensions/tree/master/Modules/System/OAuth2.
Let’s just dive into the code. If you have read the PowerShell examples or C# example code, then the code below should be familiar.
codeunit 50100 BCConnector
{
var
ClientIdTxt: Label '3870c15c-5700-4704-8b1b-e020052cc860';
ClientSecretTxt: Label '~FJRgS5q0YsAEefkW-_pA4ENJ_vIh-5RV9';
AadTenantIdTxt: Label 'kauffmann.nl';
AuthorityTxt: Label 'https://login.microsoftonline.com/{AadTenantId}/oauth2/v2.0/token';
BCEnvironmentNameTxt: Label 'sandbox';
BCCompanyIdTxt: Label '64d41503-fcd7-eb11-bb70-000d3a299fca';
BCBaseUrlTxt: Label 'https://api.businesscentral.dynamics.com/v2.0/{BCEnvironmentName}/api/v2.0/companies({BCCompanyId})';
AccessToken: Text;
AccesTokenExpires: DateTime;
trigger OnRun()
var
Customers: Text;
Items: Text;
begin
Customers := CallBusinessCentralAPI(BCEnvironmentNameTxt, BCCompanyIdTxt, 'customers');
Items := CallBusinessCentralAPI(BCEnvironmentNameTxt, BCCompanyIdTxt, 'items');
Message(Customers);
Message(Items);
end;
procedure CallBusinessCentralAPI(BCEnvironmentName: Text; BCCompanyId: Text; Resource: Text) Result: Text
var
Client: HttpClient;
Response: HttpResponseMessage;
Url: Text;
begin
if (AccessToken = '') or (AccesTokenExpires = 0DT) or (AccesTokenExpires > CurrentDateTime) then
GetAccessToken(AadTenantIdTxt);
Client.DefaultRequestHeaders.Add('Authorization', GetAuthenticationHeaderValue(AccessToken));
Client.DefaultRequestHeaders.Add('Accept', 'application/json');
Url := GetBCAPIUrl(BCEnvironmentName, BCCompanyId, Resource);
if not Client.Get(Url, Response) then
if Response.IsBlockedByEnvironment then
Error('Request was blocked by environment')
else
Error('Request to Business Central failed\%', GetLastErrorText());
if not Response.IsSuccessStatusCode then
Error('Request to Business Central failed\%1 %2', Response.HttpStatusCode, Response.ReasonPhrase);
Response.Content.ReadAs(Result);
end;
local procedure GetAccessToken(AadTenantId: Text)
var
OAuth2: Codeunit OAuth2;
Scopes: List of [Text];
begin
Scopes.Add('https://api.businesscentral.dynamics.com/.default');
if not OAuth2.AcquireTokenWithClientCredentials(ClientIdTxt, ClientSecretTxt, GetAuthorityUrl(AadTenantId), '', Scopes, AccessToken) then
Error('Failed to retrieve access token\', GetLastErrorText());
AccesTokenExpires := CurrentDateTime + (3599 * 1000);
end;
local procedure GetAuthenticationHeaderValue(AccessToken: Text) Value: Text;
begin
Value := StrSubstNo('Bearer %1', AccessToken);
end;
local procedure GetAuthorityUrl(AadTenantId: Text) Url: Text
begin
Url := AuthorityTxt;
Url := Url.Replace('{AadTenantId}', AadTenantId);
end;
local procedure GetBCAPIUrl(BCEnvironmentName: Text; BCCOmpanyId: Text; Resource: Text) Url: Text;
begin
Url := BCBaseUrlTxt;
Url := Url.Replace('{BCEnvironmentName}', BCEnvironmentName)
.Replace('{BCCompanyId}', BCCOmpanyId);
Url := StrSubstNo('%1/%2', Url, Resource);
end;
}
I’ve tried to keep the code close to the C# example. There is definitely room to improve and JSON handling should be added here as well. Secrets should not be stored in code like this. For the Business Central SaaS environment, I would definitely go with Azure Key Vault storage.
The only thing I was really missing is handling the lifetime of the access token. The OAuth2 codeunit just returns an access token without any information about the expiration. In the code above I’ve added that myself by assuming the default lifetime of 60 minutes (access tokens are usually returned with expires in = 3599 seconds).
Another small thing I noticed is the RedirectURL parameter for the function AcquireTokenWithClientCredentials. That doesn’t make sense, there is no redirect URL used during the client credentials flow. So I passed in an empty string, and luckily that worked. The parameter could be completely removed in my opinion.
That’s it! With this blog post I finish the series about using the client credentials flow with Business Central APIs. But I’m not done with OAuth, not by far! I’d like to write something about setting up OAuth for on-prem installations as well. And I’m open for suggestions, just shoot me a message!
Original Post https://www.kauffmann.nl/2021/08/04/service-to-service-authentication-in-business-central-18-3-how-to-use-in-al/






