AlexAcc
AlexAcc

Reputation: 621

Unauthorized request in Sharepoint API with MSAL token

Background I'm developing a mobile app which authenticate with Azure Active Directory (Microsoft Authentication), and have access to some information in Sharepoint. My first choice was to use Ionic Cordova, but Cordova-MSAL integration seems to be not supported. So we have chosen Xamarin, in order to develop an cross plataform app, but compatible with Microsoft authentication.

Situation I am trying to connect with Sharepoint API.

1- I have registered a new app in Azure (Active Directory), and given permissions.

2- I am getting the bearer token with MSAL library (in web and with Xamarin), after logging in myself (like in the link below): https://learn.microsoft.com/es-es/azure/active-directory/develop/quickstart-v2-javascript

3- Now I'm making the following request to Sharepoint API.

url: http://site url/_api/web/lists(guid'list GUID'),
method: GET
Headers:
    Authorization: "Bearer " + accessToken
    accept: "application/json;odata=verbose" 

BUT I'm always getting the following error:

{"error_description":"Invalid JWT token. No certificate thumbprint specified in token header."}

I'm reading a lot of people talking about errors with MSAL, but is the official way (ADAL looks like about to be deprecated).

Any ideas will be appreciated, thanks a lot.

Upvotes: 1

Views: 4209

Answers (2)

Debro012
Debro012

Reputation: 119

I too was facing this issue, when utilizing MSAL.js to authenticate and acquire an access token to make successful calls to the SharePoint Online API.

The following documentation from Microsoft provided an awesome example and explanation of authentication via MSAL: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spa

However, I needed to move forward with utilizing the acquired tokens to call the SharePoint API directly--rather than consuming the services via the Graph API.

To resolve the problem, I had to define my scopes as follows:

scopes: [https://{tenantName}.sharepoint.com/.default]

Using the example from the Microsoft article, I made the necessary changes. I was able to utilize the acquired access token to successfully make calls to the SharePoint API:

<!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.0/js/msal.js"> 
</script>
</head>
<body>
    <h2>Welcome to MSAL.js Quickstart</h2><br />
    <h4 id="WelcomeMessage"></h4>
    <button id="SignIn" onclick="signIn()">Sign In</button><br /><br />
    <pre id="json"></pre>
    <script>

    var msalConfig = {
        auth: {
            clientId: "{clientID}",
            authority: "https://login.microsoftonline.com/organizations"
        },
        cache: {
            cacheLocation: "localStorage",
            storeAuthStateInCookie: true
        }
    };

    var sharePointConfig = {
        sharePointFileEndpoint: "https://{tenantName}.sharepoint.com/sites/{siteName}/_api/web/GetFolderByServerRelativeUrl('Shared Documents/{folderName}')/Files('testFile.txt')/$value"
    }

    //the defined scopes were updated for making calls to the SharePoint API.

    var requestObj = {
        scopes: ["https://{tenantName}.sharepoint.com/.default"]
    };

    var myMSALObj = new Msal.UserAgentApplication(msalConfig);
    // Register Callbacks for redirect flow
    myMSALObj.handleRedirectCallback(authRedirectCallBack);


    function signIn() {

        myMSALObj.loginPopup(requestObj).then(function (loginResponse) {
            //Login Success
            showWelcomeMessage();
            acquireTokenPopupAndCallAPI();

        }).catch(function (error) {
            console.log(error);
        });
    }


    function acquireTokenPopupAndCallAPI() {
        //Always start with acquireTokenSilent to obtain a token in the signed in user from cache
        myMSALObj.acquireTokenSilent(requestObj).then(function (tokenResponse) {

            //Http Request

            callAPI(sharePointConfig.sharePointFileEndpoint, tokenResponse.accessToken, graphAPICallback);

        }).catch(function (error) {
            console.log(error);
            // Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY)
            // Call acquireTokenPopup(popup window)
            if (requiresInteraction(error.errorCode)) {
                myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {

                    //Http Request

                    callAPI(sharePointConfig.sharePointFileEndpoint, tokenResponse.accessToken, graphAPICallback);

                }).catch(function (error) {
                    console.log(error);
                });
            }
        });
    }


    function graphAPICallback(data) {
        document.getElementById("json").innerHTML = JSON.stringify(data, null, 2);
    }


    function showWelcomeMessage() {
        var divWelcome = document.getElementById('WelcomeMessage');
        divWelcome.innerHTML = 'Welcome ' + myMSALObj.getAccount().userName + "to Microsoft Graph API";
        var loginbutton = document.getElementById('SignIn');
        loginbutton.innerHTML = 'Sign Out';
        loginbutton.setAttribute('onclick', 'signOut();');
    }

    function authRedirectCallBack(error, response) {
        if (error) {
            console.log(error);
        }
        else {
            if (response.tokenType === "access_token") {

                callAPI(sharePointConfig.sharePointFileEndpoint, tokenResponse.accessToken, graphAPICallback);
            } else {
                console.log("token type is:" + response.tokenType);
            }
        }
    }

    function requiresInteraction(errorCode) {
        if (!errorCode || !errorCode.length) {
            return false;
        }
        return errorCode === "consent_required" ||
            errorCode === "interaction_required" ||
            errorCode === "login_required";
    }

    // Browser check variables
    var ua = window.navigator.userAgent;
    var msie = ua.indexOf('MSIE ');
    var msie11 = ua.indexOf('Trident/');
    var msedge = ua.indexOf('Edge/');
    var isIE = msie > 0 || msie11 > 0;
    var isEdge = msedge > 0;
    //If you support IE, our recommendation is that you sign-in using Redirect APIs
    //If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check
    // can change this to default an experience outside browser use
    var loginType = isIE ? "REDIRECT" : "POPUP";

    if (loginType === 'POPUP') {
        if (myMSALObj.getAccount()) {// avoid duplicate code execution on page load in case of iframe and popup window.
            showWelcomeMessage();
            acquireTokenPopupAndCallAPI();
        }
    }
    else if (loginType === 'REDIRECT') {
        document.getElementById("SignIn").onclick = function () {
            myMSALObj.loginRedirect(requestObj);
        };
        if (myMSALObj.getAccount() && !myMSALObj.isCallback(window.location.hash)) {// avoid duplicate code execution on page load in case of iframe and popup window.
            showWelcomeMessage();
            acquireTokenPopupAndCallAPI();
        }
    } else {
        console.error('Please set a valid login type');
    }


    function callAPI(theUrl, accessToken, callback) {
        var xmlHttp = new XMLHttpRequest();
        /*
        xmlHttp.onreadystatechange = function () {
            if (this.readyState == 4 && this.status == 200)
                callback(JSON.parse(this.responseText));
        }
        */
        xmlHttp.open("GET", theUrl, true); // true for asynchronous
        xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
        xmlHttp.send();         
    }


    function signOut() {
        myMSALObj.logout();
    }

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

The above example is a modified version of the example from Microsoft. I was able to utilize this for successful testing.

Upvotes: 3

Tony Ju
Tony Ju

Reputation: 15609

The token is invalid, please check the way you got the access token. I granted AllSites.FullControl permission to the app. So the scope should be

https://{xxx}.sharepoint.com/AllSites.FullControl

The response:

enter image description here

Upvotes: 0

Related Questions