Skip to main content

Notifications

Announcements

No record found.

Small and medium business | Business Central, N...
Answered

OAuth 2.0 S2S to Dynamics BC Online

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.

  • Verified answer
    Marco Mels Profile Picture
    Marco Mels on at
    RE: OAuth 2.0 S2S to Dynamics BC Online

    Thank you for sharing final outcome!

  • Suggested answer
    SeanRBS Profile Picture
    SeanRBS 5 on at
    RE: OAuth 2.0 S2S to Dynamics BC Online

    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";

  • SeanRBS Profile Picture
    SeanRBS 5 on at
    RE: OAuth 2.0 S2S to Dynamics BC Online

    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.

  • SeanRBS Profile Picture
    SeanRBS 5 on at
    RE: OAuth 2.0 S2S to Dynamics BC Online

    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!

  • Suggested answer
    Nitin Verma Profile Picture
    Nitin Verma 21,091 Super User 2024 Season 1 on at
    RE: OAuth 2.0 S2S to Dynamics BC Online

    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;
    }

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

December Spotlight Star - Muhammad Affan

Congratulations to a top community star!

Top 10 leaders for November!

Congratulations to our November super stars!

Tips for Writing Effective Suggested Answers

Best practices for providing successful forum answers ✍️

Leaderboard

#1
André Arnaud de Calavon Profile Picture

André Arnaud de Cal... 291,280 Super User 2024 Season 2

#2
Martin Dráb Profile Picture

Martin Dráb 230,214 Most Valuable Professional

#3
nmaenpaa Profile Picture

nmaenpaa 101,156

Leaderboard

Featured topics

Product updates

Dynamics 365 release plans