1
votes

(Continuation of question: Why is my SPA, which is calling my WebAPI, using Azure Active Directory, receiving "Authorization has been denied for this request."?)

My client SPA is trying to call a protected WebAPI (service). The client uses MSAL (Micosoft Authentication Libraries). The problem happens before calling the API, i.e. in the picture below somewhere between 1 and 2.

enter image description here

Here is the client

<!DOCTYPE html>
<html>
<head>
    <title>Quickstart for MSAL JS</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js"></script>
    <script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.2/js/msal.js"></script>
</head>
<body>
    <div class="container">
        <div>
            <button id="GetTodos" onclick="getTodos()">Get Todos</button>
        </div>
    </div>
    <script>

        var msalConfig = {
            auth: {
                clientId: 'xxxxxxxxxxxxxxxxxxxxxxx2427', 
                authority: "https://login.microsoftonline.com/my.tenant"
            },
            cache: {
                cacheLocation: "sessionStorage",
                storeAuthStateInCookie: true
            }
        };


        var requestObj = {
            // scopes: ["user.read"]
            scopes: ["access_as_user"]
        };

        var myMSALObj = new Msal.UserAgentApplication(msalConfig);

        myMSALObj.handleRedirectCallback(authRedirectCallBack);

        function getTodos() {
            console.log("getTodos ...");

            myMSALObj.loginPopup(requestObj)
                .then(response => {
                    myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {

                        var xmlHttp = new XMLHttpRequest();
                        xmlHttp.onreadystatechange = function () {
                            if (this.readyState == 4 && this.status == 200) {
                                console.log("success");
                                //this.responseText
                            } else {
                                console.log("failure");
                            }
                        }
                        const apiUrl = "https://localhost:44321/api/todolist";
                        xmlHttp.open("GET", apiUrl, true); // true for asynchronous
                        xmlHttp.setRequestHeader('Authorization', 'Bearer ' + tokenResponse.accessToken);
                        xmlHttp.send();

                    }).catch(function (error) {
                        console.log(error);
                    });
                })
                .catch(err => {
                    // handle error
                    console.log("login popup failed.", err);
                });
        }


        function authRedirectCallBack(error, response) {
            console.log('authRedirectCallBack');
            if (error) {
                console.log(error);
            } else {
                if (response.tokenType === "access_token") {
                    console.log("response.tokenType === access_token");
                } else {
                    console.log("token type is:" + response.tokenType);
                }
            }
        }

    </script>
</body>
</html>

Both client and service are registered apps on Azure Active Directory

enter image description here

The client has API permissions to access the service.

enter image description here

And the service does expose an API with the scope access_as_user

enter image description here

However the call

myMSALObj.loginPopup(requestObj)

causes

ServerError: "AADSTS650053: The application 'ClientSPA' asked for scope 'access_as_user' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'.

And when using Chrome I get this message

enter image description here

Further Investigation

Instead of asking for scope „access_as_user“, I ask for „user.read“ This gives me an access token. But this causes the WebAPI to respond with

Authorization has been denied for this request

And when I decode the access token I see that the audience is https://graph.microsoft.com. But I am expecting the audience to be „https://my.tenant/TodoListService-NativeDotNet“. See picture below (the obfuscated lines do contain information that is specific to my user and my registered app)

enter image description here

Questions

  1. Where does the resource '00000003-0000-0000-c000-000000000000' ID come from (it is mentioned in the error message)? It is not the application ID of the client nor of the service.

  2. What have I done incorrectly, either in the configuration on Azure Active Directory or in the client?

  3. Could CORS be a problem? I have setup the service to allow CORS.

1
'00000003-0000-0000-c000-000000000000' is application Id of graph.microsoft.com. You can refer shawntabrizi.com/aad/…Agrawal Shraddha
So why has my app a reference to graph.microsoft.com? The authentication should go to "login.microsoftonline.com/tenant" ?robor
Because you did not specify a resource identifier, AAD is attempting to find your scope on Microsoft GraphZachafer

1 Answers

5
votes

The scope should include the exposing resource's identifier (the Application ID URI). You can find the full value by going to "Expose an API" -> Edit a Scope -> Copy the label at the top...

var requestObj = {
    // scopes: ["https://graph.microsoft.com/user.read"]
    scopes: ["https://ServiceWebAPI/TodoListService-NativeDotNet-access_as_user"]
};

Further reading on individual scopes here: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#requesting-individual-user-consent