1

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.

AlexAcc
  • 601
  • 2
  • 10
  • 28

2 Answers2

3

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.

Debro012
  • 99
  • 5
0

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

Tony Ju
  • 14,891
  • 3
  • 17
  • 31
  • Can you please confirm that using this generated token are you able to access sharepoint data? – anomepani Oct 17 '19 at 04:12
  • Can you provide reference link from where you have generated token and which authenticatiob flow? – anomepani Oct 17 '19 at 04:14
  • @anomepani I used code grant flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code – Tony Ju Oct 17 '19 at 04:30
  • @anomepani The token has `AllSites.FullControl` permission. – Tony Ju Oct 17 '19 at 04:32
  • Even if token permission if the valid audience is not there in token it will not work. – anomepani Oct 17 '19 at 07:27
  • @anomepani You are right, the scope should be 'https://{xxx}.sharepoint.com/AllSites.FullControl', just as I said in the answer. – Tony Ju Oct 17 '19 at 07:51
  • @TonyJu this post (https://stackoverflow.com/questions/40046598/msal-or-adal-library-for-use-with-azure-ad-b2c-and-xamarin?rq=1) says that ADAL (AD v1) is incompatible with Xamarin... – AlexAcc Oct 21 '19 at 15:23
  • @AlexAcc Tony means that var requestObj = { scopes: ["user.read"] }; here the scope should be https://{xxx}.sharepoint.com/AllSites.FullControl did you replace that and try ? is it giving the same error ? – biswpo Oct 21 '19 at 16:50
  • Yes, I'm sending that in scope of MSAL, and also as a param in a request to "https://login.microsoftonline.com/XXXX..." and No Luck – AlexAcc Oct 22 '19 at 08:29
  • My problem, when doing a request to login.microsoftonline.com/XXXX.. even in postman, is that I receive the html of "Microsoft Sign In" as raw body response, but no popup opening... – AlexAcc Oct 22 '19 at 09:29
  • @AlexAcc Can you provide more details? How did you get the token? The complete request url is? – Tony Ju Oct 23 '19 at 01:33