Business Central Online API Successful token but wrong credentials

Hi, I’m trying to test an API from business central online with OAuth 2.0
I created the azure app, configured the api permissions, the secrets and all that.
When I enter the values in postman, I get the token but when I call the API I’m getting the 401 error.

image

After that, I use the token and try to call the API but I get this error:

These are my values:

And I’m calling one of the standard APIs that come with business central. I just copy and paste it.

They do not known my name I will tell it it is spring time
Did anybody say Spring Time

I would advise to create environment variables for the “Client ID” and “Client Secret” and anything else that you think should be private.

The scope in your screenshot doesn’t look correct. It shouldn’t contain a URL as far as I’m aware.

If this is authenticating via Azure, then its highly likely you can use other authentication methods that are easier to consume. They are usually set at the domain level.

So although we use OpenID in our Azure Web apps, we can also use the “password” grant type which allows you to use a bearer token (and mimic a real user).

The following is an example utilizing a pre-request script.

pm.test("Check for collectionVariables", function () {
    let vars = ['clientId', 'clientSecret', 'tenantId', 'username', 'password', 'scope'];
    vars.forEach(function (item, index, array) {
        console.log(item, index);
        pm.expect(pm.collectionVariables.get(item), item + " variable not set").to.not.be.undefined;
        pm.expect(pm.collectionVariables.get(item), item + " variable not set").to.not.be.empty; 
    });

    if (!pm.collectionVariables.get("bearerToken") || Date.now() > new Date(pm.collectionVariables.get("bearerTokenExpiresOn") * 1000)) {
        pm.sendRequest({
            url: 'https://login.microsoftonline.com/' + pm.collectionVariables.get("tenantId") + '/oauth2/v2.0/token',
            method: 'POST',
            header: 'Content-Type: application/x-www-form-urlencoded',
            body: {
                mode: 'urlencoded',
                urlencoded: [
                    { key: "client_id", value: pm.collectionVariables.get("clientId"), disabled: false },
                    { key: "scope", value: pm.collectionVariables.get("scope"), disabled: false },
                    { key: "username", value: pm.collectionVariables.get("username"), disabled: false },
                    { key: "password", value: pm.collectionVariables.get("password"), disabled: false },                    
                    { key: "client_secret", value: pm.collectionVariables.get("clientSecret"), disabled: false },
                    { key: "grant_type", value: "password", disabled: false },
                ]
            }
        }, function (err, res) {
            if (err) {
                console.log(err);
            } else {
                pm.test("Status code is 200", () => {
                    pm.expect(res).to.have.status(200);
                });
                let resJson = res.json();
                pm.collectionVariables.set("bearerTokenExpiresOn", resJson.expires_in);
                pm.collectionVariables.set("bearerToken", resJson.id_token);
            }
        });
    }
});

You can also do this by using a normal post request.

But the main issue with this method is that it gets a new token each and every time which may or may not be an issue for you. The pre-request script checks the timestamp on the token and only gets a new one if a certain threshold has been hit.

You then set the token using the tests tab. (The following is just an example and doesn’t have much error handing as I prefer the pre-request script method).

response = pm.response.json();

pm.collectionVariables.set("bearerToken", response.id_token); // used in the authorisation header bearer token

pm.collectionVariables.set("bearerTokenExpiresOn", response.expires_in);

//Step 1: Define the schema

const schema = {
    'type': 'object',
    'properties': {
        'token_type': {
            type: 'string'
        },
        'scope': {
            type: 'string'
        },
        'expires_in': {
            type: 'number'
        },
        'ext_expires_in': {
            type: 'number'
        },
        'access_token': {
            type: 'string'
        },
        'id_token': {
            type: 'string'
        }
    },
    required: ['token_type', 'scope', 'expires_in', 'ext_expires_in', 'access_token', 'id_token']
};

//Step 2: Validate response against schema

if(pm.response.code===200)
    pm.test('MicrosoftOnline Login response schema is valid', () => {
        pm.expect(response).to.have.jsonSchema(schema);
    });

This way you can just hit your end point using the bearer token in the authorization options.

The point is that you are not really testing the Microsoft login as that is controlled by Microsoft, you are interested in the API that you hit after this.

Hi,
All I had to do was add ?resource=https://api.businesscentral.dynamics.com at the end of my auth URL and Access Token URL.

Ex:
https://login.microsoftonline.com/***********/oauth2/token?resource=https://api.businesscentral.dynamics.com

Thanks and I hope this helps someone!