Service-to-service authentication for automation APIs in Business Central 2020 release wave 2

14 Sep

One of the many new features in Business Central 2020 release wave 2, aka v17, is service-to-service authentication for automation APIs. What does that mean, how does it help us, and how does it work?

Automation APIs are used for automating company setup through APIs. In terms of Microsoft, they can be used to ‘hydrate the tenant’. Think about creating a new company, importing and applying RapidStart packages, manage users and permissions, and, last but not least, uploading and installing per tenant extensions. Automation APIs are widely used in DevOps pipelines to upload and install per tenant extensions.

The automation APIs support only one authentication mechanism, the bearer token. To get a bearer token, you need to go through the OAuth authentication process. Only delegated admins (in Azure Active Directory) and Business Central users with the right permissions can call the APIs. This means that the process, in most cases PowerShell code executed by a DevOps agent, needs to impersonate a delegated admin or Business Central user. This requires a setup process to let the user first authorize the app to access Business Central on his behalf. This is also known as the consent process. Because the code that calls the APIs normally doesn’t run under the user credentials, they need to work with a refresh token, store it in a safe place and use the refresh token to get a new token whenever they call Business Central APIs.

This is going to change. In version v17 it will be possible to let the app identify himself with his secret value and get a token without needing a refresh token. The consent process will also be different. The external application that calls the automation APIs needs to be registered inside Business Central where it can also be given consent. After that, the external application can call automation APIs without any further setup requiring user interaction and without refresh tokens.

Let’s look at how that works. Imagine a PowerShell script (the external application) that wants to upload a per tenant extension. The process is as follows:

  • Register the external application in Azure Active Directory
  • Register the external application in Business Central and run the consent flow to authorize it
  • External application calls the automation API

Register the external application in Azure Active Directory

This step has to be done by the company owning the external application that will call Business Central automation APIs.

Navigate to Azure portal and open Azure Active Directory. Click on App registrations in the menu and then on New registration. Fill in a name and the supported account type. For supported account type you should choose from the first two options:

  • Accounts in this organizational directory only ([organization] – Single tenant)
  • Accounts in any organizational directory (Any Azure AD directory – Multitenant)

Choose the first option if your external application will only be used inside your own organization. If you intend to let other organizations use your external application, then choose the second option.

On this screen you should also fill in the Redirect URI. Enter this URL into the field: https://businesscentral.dynamics.com/OAuthLanding.htm.

Click on Register to create the application. Make a copy of the Application (client) ID from the overview screen. You will need this later when registering the app in Business Central.

The next step is to set the API permissions that the external application needs. If you are already familiar with OAuth and Business Central APIs, then you will see that there is a difference in this step. Click on API permissions in the menu and then on Add a permission. From the list of commonly used Microsoft APIs, you select Dynamics 365 Business Central. On a side note, it still doesn’t have the new logo. Did Microsoft forget that? Anyway, now comes the difference. Instead of selecting delegated permissions you must select Application permissions. As the description says, this is for applications that runs as a background service without a signed-in user. From the list of permissions select Automation.ReadWrite.All.

This permission is described in the details of this new feature. You’ll notice another permission here, called app_access. It appears that this is a standard permission automatically added by Azure. This permission is not for us, Microsoft is still working on license options for background processes that access Business Central data as a non-interactive user. That scenario is definitely not going to be supported in v17. But I really hope we will see that soon.

Anyway, don’t forget to click on the button Add permissions to save the settings. You should now have a screen like this:

Last step in registering the app in Azure is to create a secret. Click on Certificates & secrets in the menu and then click on New client secret. Choose whatever expiration period you want and click on Add. Don’t forget to make a copy of the created secret because this is only time you will be able to see it.

We are now done with the registration in Azure and we can head to Business Central to register the application.

Register the external application in Business Central

In Business Central search for AAD Applications and open the page. Click on New to add a new record. Fill in the Client Id that you copied in the previous step or received from the organization that owns the external application. A description is also needed.

The next step is to grant consent. Click on the Grant Consent action in the top. You will now get a page that requires you to log in. The user that gives consent needs to be a Global Administrator, an Application Administrator, or a Cloud Application Administrator. If you don’t have that role, then let a user who has the required role log in here. This is only needed in this step of the registration process. After logging in you will get a page that asks for the permission we have set earlier.

Click on Accept and the page will close and you will get a confirmation message in Business Central.

Last step is to give this application permission inside Business Central. Under the User Permission Sets add these two permission

  • D365 AUTOMATION
  • D365 EXTENSION MGT

Now the external application has been fully set up to access Business Central automation APIs. We can move on to the external application and call the automation APIs.

External application calls the automation API

We are going to use PowerShell to call automation APIs. First example is for Windows PowerShell 5.1. You can use this code in the good old Windows PowerShell ISE. Don’t worry about my client secret, that one is already invalidated. 😉

What I would like to indicate here is the tenandId. It’s just the domain instead of a GUID. Both options work, but this is more readable to me. But, more importantly, the registration in Azure in the first step was done in another AAD tenant (named Cronus Company). Because the external application (the script below) was registered as a multitenant application I can use it for other tenants.

#########################
# PowerShell example
#########################

$ClientID     = "0e654beb-85b9-4f3c-8e53-0601d4bd3c15"
$ClientSecret = "_g1L8xZyPCc-~UFW24WUggNPjKGak4~y7r"
$loginURL     = "https://login.microsoftonline.com"
$tenantId     = "kauffmann.nl"
$scopes       = "https://api.businesscentral.dynamics.com/.default"
$environment  = "v17"
$baseUrl      = "https://api.businesscentral.dynamics.com/v2.0/$environment/api/microsoft/automation/v1.0"
$appFile      = "D:\Repos\Local\AL\ALProject254\Default publisher_ALProject254_1.0.0.0.app"

# Get access token 
$body = @{grant_type="client_credentials";scope=$scopes;client_id=$ClientID;client_secret=$ClientSecret}
$oauth = Invoke-RestMethod -Method Post -Uri $("$loginURL/$tenantId/oauth2/v2.0/token") -Body $body

# Get companies
$companies = Invoke-RestMethod `
             -Method Get `
             -Uri $("$baseurl/companies") `
             -Headers @{Authorization='Bearer ' + $oauth.access_token}

$companies.value | Format-Table -AutoSize
$companyId = $companies.value[0].id

# Upload and install app
Invoke-RestMethod `
-Method Patch `
-Uri $("$baseurl/companies($companyId)/extensionUpload(0)/content") `
-Headers @{Authorization='Bearer ' + $oauth.access_token;'If-Match'='*'} `
-ContentType "application/octet-stream" `
-InFile $appFile

The script above needs to handle the expiration of the token itself. Most probably this results in getting a new token for every time the script runs. It’s recommended to install the MSAL.PS module. This PowerShell module has a function Get-MsalToken which handles the call to Azure to acquire the token and it uses a cache to securely safe and reuse the token until it expires. To install the module run this command:

Install-Module -name MSAL.PS -Force -AcceptLicense

Now the script can be changed to this. Note that the variable loginURL has been removed, that’s not needed anymore.

##############################
# PowerShell example with MSAL
##############################

$ClientID     = "0e654beb-85b9-4f3c-8e53-0601d4bd3c15"
$ClientSecret = "_g1L8xZyPCc-~UFW24WUggNPjKGak4~y7r"
$tenantId     = "kauffmann.nl"
$scopes       = "https://api.businesscentral.dynamics.com/.default"
$environment  = "v17"
$baseUrl      = "https://api.businesscentral.dynamics.com/v2.0/$environment/api/microsoft/automation/v1.0"
$appFile      = "D:\Repos\Local\AL\ALProject254\Default publisher_ALProject254_1.0.0.0.app"

# Get access token 
$token = Get-MsalToken `
         -ClientId $ClientID `
         -TenantId $tenantId `
         -Scopes $scopes `
         -ClientSecret (ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force)

# Get companies
$companies = Invoke-RestMethod `
             -Method Get `
             -Uri $("$baseurl/companies") `
             -Headers @{Authorization='Bearer ' + $token.AccessToken}

$companies.value | Format-Table -AutoSize
$companyId = $companies.value[0].id

# Upload and install app
Invoke-RestMethod `
-Method Patch `
-Uri $("$baseurl/companies($companyId)/extensionUpload(0)/content") `
-Headers @{Authorization='Bearer ' + $token.AccessToken;'If-Match'='*'} `
-ContentType "application/octet-stream" `
-InFile $appFile

If you are using PowerShell Core, then the script can be simplified further because of some new parameters for authentication. Instead of using the Headers parameter for authentication, we can use the Authentication and Token parameter. It’s cosmetic of course, but improves the readability.

###################################
# PowerShell Core example with MSAL
###################################

$ClientID     = "0e654beb-85b9-4f3c-8e53-0601d4bd3c15"
$ClientSecret = "_g1L8xZyPCc-~UFW24WUggNPjKGak4~y7r"
$tenantId     = "kauffmann.nl"
$scopes       = "https://api.businesscentral.dynamics.com/.default"
$environment  = "v17"
$baseUrl      = "https://api.businesscentral.dynamics.com/v2.0/$environment/api/microsoft/automation/v1.0"
$appFile      = "D:\Repos\Local\AL\ALProject254\Default publisher_ALProject254_1.0.0.0.app"

# Get access token 
$token = Get-MsalToken `
         -ClientId $ClientID `
         -TenantId $tenantId `
         -Scopes $scopes `
         -ClientSecret (ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force)

# Get companies
$companies = Invoke-RestMethod `
             -Method Get `
             -Uri $("$baseurl/companies") `
             -Authentication OAuth `
             -Token (ConvertTo-SecureString -String $token.AccessToken -AsPlainText -Force)

$companies.value | Format-Table -AutoSize
$companyId = $companies.value[0].id

# Upload and install app
Invoke-RestMethod `
-Method Patch `
-Uri $("$baseurl/companies($companyId)/extensionUpload(0)/content") `
-Authentication OAuth `
-Token (ConvertTo-SecureString -String $token.AccessToken -AsPlainText -Force) `
-Headers @{"If-Match"="*"} `
-ContentType "application/octet-stream" `
-InFile $appFile

Final comments

I can see two benefits of this new feature. From an end-user point of view, they only have to do the registration in Business Central. This can be documented and should be easy to complete. And the external application does not have to implement a user interface to acquire a token and refresh token and store them in a safe place. That’s a win-win I would say!

The AAD Application card in Business Central contains a field for the App Id and App Name. It suggests that apps can register AAD applications themselves. I’ve not seen any information about doing that automatically as you can do with web services. But the registration of the AAD application in Business Central can also be done by AL code. From an install upgrade Codeunit you can call the function CreateAADApplication in Codeunit “AAD Application Interface” to do so. This does NOT automatically grant consent but is convenient for the end-user. The only step he needs to do is the grant consent flow. Make sure to add the extension info after the call, because the code doesn’t support that (well, not in the v17 preview, that is).

If the external application will be used inside your organization only, then you can select single tenant when registering the app in Azure. After setting the permissions in Azure, there is a button Grant admin consent for [organization]. You can use that instead of going through the grant consent flow in Business Central. But again, that only grants consent inside your organization. To get access to Business Central the app still needs to be registered in Business Central and get the right permission sets. The only difference is that you can skip the grant consent flow in Business Central in case you did that already in the Azure portal. Did I already say that this is only for inside your organization?

CSPs can also pre-consent an app for partner AADs that they are managing by adding the AAD application to the Adminagents group in the partner tenant. For more information see https://docs.microsoft.com/en-us/graph/auth-cloudsolutionprovider.

I hope this explanation was clear enough to get you going with this new feature in Business Central!

5 thoughts on “Service-to-service authentication for automation APIs in Business Central 2020 release wave 2

  1. Hi,

    Really looking forward to this new feature! Nice article.
    However , I have a couple of doubts and I am unable to find the answers in Microsoft’s documentation.

    There are some entities in which the creating user is stored. Which user would it be in this case? Is it the user that grants consent to the app?

    And I have another doubt regarding the licenses. Will this feature consume any type of license?

    Thanks for the article,
    Jon Ander

    • With Automation APIs you don’t directly access any data as you do with the standard APIs. In other words, you don’t create entities in normal tables.
      As far as I know, Automation APIs don’t consume any license. But until v16 you had to use impersonation to all the automation APIs, and that requires an existing user of course. But from v17 that’s not the case anymore.

  2. Is this only going to be valid fo automation scenarios?
    Does this mean that I cannot create an external service to get the Item data, for example, and use the service-to-service authentication?

    • Yes, that’s right. Microsoft is working an enabling it for data access, but that requires some license conditions as well. Maybe it comes with an update on v17 or we have to wait for v18.

  3. Thanks for this information. I am attempting to run this using a Confidential Client Application Builder in C# to download a chart of accounts and I am getting an error stating “You do not have the following permissions on Page APIV1 – Accounts: Execute.”

    Any ideas on how I can grant these permission to a non-interactive login?

Leave a Reply to ajk Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.