Service-to-service authentication for automation APIs in Business Central

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!

40 thoughts on “Service-to-service authentication for automation APIs in Business Central

  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.

      • It’s now Jan 2021 and we’re being warned that Web Service Access Keys are soon going to be phased out, and OAuth seemingly can’t be used for data access. Is unattended data access going to be impossible? Seems pretty awkward…

        • You are right, it’s too bad that we still don’t have service-to-service authentication for data access. I’m planning to write a blog post about it in the next few days.
          But… I’ve also found a way to get access with an OAuth token without any user interaction and without storing a refresh token. Stay tuned, that will also be in the blog post as a workaround.

  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?

  4. Pingback: My “step by step” guide for implementing OData API authorisation in Business Central v17.x – Yet another www.business-central.blog

  5. Pingback: My “step by step” guide for implementing OData API authorisation in Business Central v17.x (Part 2) – Yet another www.business-central.blog

  6. Pingback: Deprecation of Basic Auth for SaaS has been postponed to 2022 - Kauffmann @ Dynamics NAV - Dynamics 365 Business Central/NAV User Group - Dynamics User Group

  7. Hello, I hope I get a reply. I’ve enjoyed your very educational post. I’ve been able to follow the steps up until where I get an auth token using OpenAuth2 for this in postman. However, I can’t for the life of me get the ODATA V4 to work with it. I’m just calling the basic customer card or companies API in BC: https://api.businesscentral.dynamics.com/v2.0/{sandbox Id removed}/Sandbox/ODataV3/Companies

    I get 401 unauthorized response. Is there some specific settings/configuration in BC I’m missing to be able to use OpenAuth2 with ODATA V4?

    • To begin with, you are calling ODataV3 endpoint. Try to change that to ODataV4.
      But why would you call the OData endpoint anyway? I suggest you switch to api/v2.0 instead.

      Then again, the service-to-service authentication isn’t enabled for the /api endpoint yet. I guess you are using the authorization grant flow from Postman, is that correct?

  8. Thanks for the reply. I posted the wrong url here during copy/paste here. https://api.businesscentral.dynamics.com/v2.0/{removed ID}/Sandbox/ODataV4/

    Yes, I’m using the authorization grant flow as it seems this is what would be the way going forward wiht OpenAuth2. I figured out the issue was with the scope I was passing. In case anyone else runs into the same issue the scope needs to be this: https://api.businesscentral.dynamics.com/.default

  9. Thank you Kauffmann, i was able to go through the process. Is there anything on data access using this auth type? It’s March, and we’re planning to implement a integration with external system using api on SaaS.

    • Yes, I’ve tested with the online sandbox for v18 last week. And I’ve been able to query the data APIs with the client credentials flow. But I had to set the same permissions (Automation.ReadWrite.All). Which seems strange to me, so I’m waiting for more information from Microsoft.

        • It worked as I described in this blog post. But that’s only temporarily. Microsoft is working out things as we speak. Please stay tuned until they have disclosed more information.

  10. Hi AJK,

    Many thanks for this article – very useful !

    We have set this up as explained and trying to test it using Postman but get this error:

    “you do not have access to this object using an application as authentication”

    Do you know what could be the reason for this error ?

    I tried to search on the net and found a few people have experience it but there could not find any resolution in the articles so far.

    Many thanks.

    Bhaskar

  11. Hi Bhaskar, have you read this blog’s other posts on this subject? They talk about permissions and testing.

    Pleased to say that everything has been working for me since the update.

  12. Hi petererer,

    Glad to hear it is working for you.

    If you can point out specific post/posts I will read them again – thanks.

    When you write : “… working for me since the update…” – which update are you referring to ?

    We are trying to use in with an Embed App that where the client runs on BC17.1

    Many thanks – Bhaskar

  13. Apologies I assumed it was similar to my case of wanting to access the regular API (which requires the July 2021 v18.3 update) rather than the automation API which this post was about.

  14. Hi AJK,

    We are calling the endpoint of our Embed App (4PS) and it is version 17.1 and on SaaS platform.

    PS: You had trained us on AL development at Ede approx. 2.5 years ago 🙂

    Many thanks – Bhaskar

    • Yes, I remember!

      For service to service authentication you need version 18.3.
      I can confirm it works with version 18.3 on the embed app platform.

  15. Hi AJK,

    Many thanks and I understand.
    We will retry when we have BC18.3 version of our App.

    Regards – Bhaskar

  16. Pingback: Service-to-service authentication for automation APIs in Business Central 2020 release wave 2 – Kauffmann @ Dynamics 365 Business Central – Thinking Enterprise Solutions https://www.vizorsol.com

  17. I’m having the same issue with “You do not have access to this object using an application as authentication.”.
    Thought the problem was because I was on 18.0 onprem but after upgrade to 18.3 tonight, the issue persists.

    The problem only occurs on a custom api with S2S auth (client_credentials flow). Same auth works fine on std. API v2.0.
    The custom API works fine with Basic Auth

  18. Great reading, still unsure about “The AAD Application card in Business Central contains a field for the App Id and App Name”.

    As opposed to online 18.3, for on-premises OAuth works on Version: W1 17.5 (Platform 17.0.30412.0 + Application 17.5.22499.23314) for Automation, standard and custom APIs (Data). For what its worth…

  19. Hey! for an existing powershell script that still uses Web Service key, do you have any idea how to convert it to Token? I’m already picking up the correct token, I just don’t know how to pass it to the credentials(this is to access a BC function within a published codeunit ):

    #Web service Key#$Credential = [System.Management.Automation.PSCredential]::new($user,(ConvertTo-SecureString $wsKey -AsPlainText -Force))
    #with Token, attempt to use AppID and token:
    $Credential = [System.Management.Automation.PSCredential]::new($ClientID,(ConvertTo-SecureString $token.AccessToken -AsPlainText -Force))

    try
    $Client = New-WebServiceProxy -Uri $url -Credential $Credential

    • It seems that New-WebServiceProxy has no option to work with an access token. The only recommendation I can make is using Invoke-WebRequest and build your SOAP calls by hand

Leave a Reply

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