Error on API call to localhost using MicrosoftIdentityWebApp

I’m running into an issue I can’t seem to find any information on. I’ve built a .Net Core web app using the Microsoft Identity Platform, and I registered it as an Azure AD application and exposed an API on it using the Azure portal. Basically I followed the exact same setup for my application as in this Microsoft Graph example:Use Postman with the Microsoft Graph API - Microsoft Graph | Microsoft Learn

I’m actually able to get the Microsoft Graph example app to work correctly and return my profile data with an API call, but when I try it with my own application that is running on my localhost dev IIS server, I get this back UNABLE_TO_VERIFY_LEAF_SIGNATURE:

image

I know the API is working correctly because it returns data in a web browser, after authentication, correctly. It just doesn’t work in Postman.

What seems to happen is, after I place a GET using Postman, it redirects to login.microsoftonline.com for some reason:
image

And that call returns a “Sign in to your account” page as the Response in Postman.

I’m able to get a token without any problem using this Authorization configuration in Postman:

I’ve tried importing my localhost certificate as a .pfx file from my certlm, didn’t do anything. I’ve tried messing around with the certificates in Postman, but I haven’t been able to figure out what the problem is.

I connect to Azure Web Apps and I’m assuming its a similar process.

I have two working methods.

  1. Using form data as the body of the request.

With the following code in the Tests tab to set the bearer token for the next request.

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

The next request just needs to have the bearer token set to the variable. This will then bypass the Microsoft login and take you directly to your API data.

  1. Use a pre-request script and sendRequest() instead.

I prefer this option as it allows you some control over the token expiry.

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

It won’t be exactly the same for you, but hopefully will give you some options to try.

Both of these example are based on the premise that you are testing the application, not the authentication per se (which is hosted by Microsoft).

Thanks for the reply mdjones. I was unable to get the first method to work. I’m currently trying to get the second method to work, but it’s returning a bad username/password error on the first login when I try to authenticate using my Microsoft SSO account that I typically use to log into the tenant with.

Any ideas?

Our APP in Azure is set for Username\password using the grant_type password.

You need to include the grant type setup and scope for your application, which I suspect is different.

It took me a while to get the right combination.