web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Small and medium business | Business Central, N...
Answered

OAuth 2.0 S2S to Dynamics BC Online

(0) ShareShare
ReportReport
Posted on by 5

I am trying to get OAuth working with BC online, using POSTMAN (and have also tried dotnetcore). I'd appreciate any help, if anyone can see something I am doing wrong?


I have tried following a number of guides, videos, and Microsoft docs, and I can get an access token back, but am rejected with 401 on any API calls I attempt. Both POSTMAN and the dotnetcore app appear to generate tokens that seem valid, but they do not let me in.

I'll also note that this is a sandbox environment, acquired via the link here: https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-get-started but I've named it "Production", which is why you'll see that in the queries.

Here are the links I've followed:

automation-apis-using-s2s-authentication
Youtube walkthrough by Erik Hougaard

I've registered Apps in Azure AD with the appropriate Application permissions (I do not want to use delegated, as I don't want a user to have to sign in):

pastedimage1664667307947v3.png

I've registered my AAD in BC with basically all permissions possible:

pastedimage1664667360895v1.png

 

I've tried to use the Grant Consent functionality via BC, and it ostensibly worked with the redirect URL I set up (still 401'd though), and I have tried without a redirect URL.
I set the app up as a single tenant, I've also tried it as a multi-tenant. Nothing seems to work when I simply try to call any API endpoint like so:

https://api.businesscentral.dynamics.com/v2.0/myGUID/Production/api/v2.0/companies
https://api.businesscentral.dynamics.com/v2.0/myGUID/Production/api/v2.0

Have also tried the odata endpoints.

This should be pretty straightforward so I'm not sure what I am missing? Anyone able to help? The latest jwt.ms decoding I have looks like this:

{
"typ": "JWT",
"alg": "RS256",
"x5t": "2ZQpJ3UpbjAYXYGaXEJl8lV0TOI",
"kid": "2ZQpJ3UpbjAYXYGaXEJl8lV0TOI"
}.{
"aud": "00000002-0000-0000-c000-000000000000",
"iss": "https://sts.windows.net/myGuid/",
"iat": 1664660352,
"nbf": 1664660352,
"exp": 1664664252,
"aio": "E2ZgYOAw1rymduL8q00/5jz4/z21FgA=",
"appid": "myCorrectAppId",
"appidacr": "1",
"idp": "https://sts.windows.net/myGuid/",
"oid": "oid",
"rh": "0.AVkABukwYKJWB0CCNK-Tk2gaCgIAAAAAAAAAwAAAAAAAAACdAAA.",
"sub": "beb41889-d36a-4ba3-98dd-fc8aa8b99a15",
"tenant_region_scope": "NA",
"tid": "myGUID",
"uti": "LEDbpEovbE2Nks34QjYTAA",
"ver": "1.0"
}.[Signature

In case it is useful here is the C# app:

     HttpClient client = new HttpClient();
         string tenantId = "redactedTenantId";
         string url = "https://login.microsoftonline.com/{tenantId}/oauth2/token";
         string clientId = "redactedClientId";
         string secret = "redactedSecret";
         var values = new Dictionary
         {
             {"grant_type", "client_credentials" },
             {"client_id", clientId},
             {"client_secret", secret},
             {"scope", "https://api.businesscentral.dynamics.com/.default"}
          };
         var content = new FormUrlEncodedContent(values);
         var response = client.PostAsync(url, content);
         var respString = response.Result.Content.ReadAsStringAsync().Result;
         JObject val = JObject.Parse(respString);
         string token = val["access_token"].ToString();

And as you can imagine I tried a similar set-up using POSTMAN's OAuth 2.0.

I have the same question (0)
  • Suggested answer
    Nitin Verma Profile Picture
    21,708 Moderator on at

    Hi,

    Please check with below code if it works. because below code is working in my case. Also check in Azure you should have Graph Read and Write permission as well.

    rocedure GenerateAccessToken()
    var
    APIConfiguration: Record "EzyVet API Configuration";
    begin
    APIConfiguration.Get();
    APIConfiguration.TestField(partner_id);
    APIConfiguration.TestField(client_id);
    APIConfiguration.TestField(client_secret);
    APIConfiguration.TestField(grant_type);
    APIConfiguration.TestField(scope);
    APIConfiguration.TestField(access_token_endpoint_url);

    gbodyContent := StrSubstNo('partner_id=%1&client_id=%2&client_secret=%3&grant_type=%4&scope=%5',
    APIConfiguration.partner_id, APIConfiguration.client_id, APIConfiguration.client_secret,
    APIConfiguration.grant_type, APIConfiguration.scope);
    gHttpcontent.WriteFrom(gbodyContent);
    gHttpcontent.GetHeaders(gHttpheaders);
    gHttpheaders.Remove('Content-Type');
    gHttpHeaders.Add('Content-Type', 'application/x-www-form-urlencoded');
    if gHttpclient.Post(APIConfiguration.access_token_endpoint_url, gHttpcontent, ghttpResponseMessage) then begin
    ghttpResponseMessage.Content.ReadAs(gresponseText);
    gJSONToken.ReadFrom(gresponseText);
    gJSONObject := gJSONToken.AsObject();
    APIConfiguration.access_token := GetJSONToken(gJSONObject, 'access_token').AsValue().AsText();
    APIConfiguration.Modify();
    Message('Access Token Retrieved');
    end else
    Error(FORMAT(ghttpResponseMessage.Content));

    end;

    local procedure GetJSONToken(JsonObject: JsonObject;
    TokenKey: Text) JsonToken: JsonToken;
    var
    begin
    if not JsonObject.get(TokenKey, JsonToken) then Error('Could not find a token with key %1', TokenKey);
    end;

    var
    ghttpClient: HttpClient;
    ghttpContent: HttpContent;
    ghttpHeaders: HttpHeaders;
    ghttpRequestMessage: HttpRequestMessage;
    ghttpResponseMessage: HttpResponseMessage;
    gbodyContent: Text;
    gresponseText: Text;
    gJSONToken: JsonToken;
    gJSONObject: JsonObject;
    }

  • SeanRBS Profile Picture
    5 on at

    Sorry:

    "Also check in Azure you should have Graph Read and Write permission as well." What Graph Read and Write is needed exactly? I tried a few that looked reasonable, but there are dozens of possibilities in Azure for adding Graph permissions.

    I'll give your code a go, or something like it -I guess you're just adding in a partner_id? Are your urls similar to mine? And your scopes and grant type?

    To clarify, you are also doing S2S above? There isn't a way for me to tell without knowing the grant type I don't think.

    Appreciate the help!

  • SeanRBS Profile Picture
    5 on at

    An update for anyone else who may be unfortunate enough to hit this in the future, when I swapped to using Microsoft.Identity.Client, my token response changed a bit an gave me back a more sensible aud of "api.businesscentral.dynamics.com".

    That said I am still getting a 401 :(. Will report back if I figure it out.

  • Suggested answer
    SeanRBS Profile Picture
    5 on at

    Ok! On a whim I decided to make a new client secret - and it was a bit flaky initially (MSAL telling me I should double check the secret etc) but after waiting about 3 minutes, I can now make a successful GET request to:

    api.businesscentral.dynamics.com/.../companies";

  • Verified answer
    Marco Mels Profile Picture
    on at

    Thank you for sharing final outcome!

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Neeraj Kumar – Community Spotlight

We are honored to recognize Neeraj Kumar as our Community Spotlight honoree for…

Leaderboard > Small and medium business | Business Central, NAV, RMS

#1
OussamaSabbouh Profile Picture

OussamaSabbouh 2,238

#2
YUN ZHU Profile Picture

YUN ZHU 773 Super User 2025 Season 2

#3
Sumit Singh Profile Picture

Sumit Singh 630

Last 30 days Overall leaderboard

Featured topics

Product updates

Dynamics 365 release plans