Reputation: 621
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
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
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:
Upvotes: 0