content.js This is the content page for chrome extension
document.getElementById("signIn").addEventListener("click", function(){
chrome.runtime.sendMessage({task:"switchUser", user: current_user},function(response){
});
});
background.js This is the background page for chrome extension
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse){
if(request.task == "switchUser"){
function getToken(){
chrome.identity.getAuthToken({ interactive: true }, function(token) {
sendResponse(token);
});
}
chrome.identity.removeCachedAuthToken({ token:
currentSessionAccessToken }, getToken);
}
return true;
});
Previous OAuth token is successfully removed but when generating a new one using getAuthToken, the user selection list is not shown. However, I have set interactive to true. What am I missing?
You need to revoke the token first and then remove from cache. Please find the code below for background.js page.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse){
if(request.task == "switchUser"){
function getToken(){
chrome.identity.getAuthToken({ interactive: true }, function(token) {
sendResponse(token);
});
}
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', 'https://accounts.google.com/o/oauth2/revoke?token=' + currentSessionAccessToken); //revoke the token calling this url.
xmlHttp.onload = function() {
chrome.identity.removeCachedAuthToken({ token: currentSessionAccessToken }, getToken); //after token is revoked, remove it from cache as well.
}
xmlHttp.onerror = function(){
};
xmlHttp.send();
}
return true;
});
Related
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.
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.
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:
First things first, I am below a novice level when it comes to Nodejs/msal/azure b2c and I am trying to understand the flow.
I started off with this sample here: https://azure.microsoft.com/en-us/resources/samples/active-directory-b2c-javascript-msal-singlepageapp/
I am using msal.js with azure ad b2c in a Nodejs application. I am redirecting the user after signing in via sign-in policy to a different page where I have my other policies.
//index.html
var clientApplication = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, authCallback, { logger: logger, cacheLocation: 'localStorage' });
function authCallback(errorDesc, token, error, tokenType) {
if (token) {
logMessage(token + ":" + token);
}
else {
logMessage(error + ":" + errorDesc);
}
}
This is my onclick login function in the index.html. The 'test(accessToken)' method does the redirection to the backend node js routes where I store the accesstoken in a session variable and the method renders to a different page(test.ejs) where my other b2c policies are stored.
function login() {
clientApplication.loginPopup(applicationConfig.b2cScopes).then(function (idToken) {
clientApplication.acquireTokenSilent(applicationConfig.b2cScopes).then(function (accessToken) {
test(accessToken);
}, function (error) {
clientApplication.acquireTokenPopup(applicationConfig.b2cScopes).then(function (accessToken) {
updateUI();
}, function (error) {
logMessage("Error acquiring the popup:\n" + error);
});
})
}, function (error) {
logMessage("Error during login:\n" + error);
});
}
Now my question is how can I retrieve the current state of the clientApplication Msal.UserAgentApplication object in my other view(test.ejs) to do something like this :
clientApplication.acquireTokenSilent(applicationConfig.b2cScopes).then(function (accessToken) {
logMessage(accessToken);
}
I've had a long-running Chrome Extension where I'm used to always have access to the background.js page for viewing console, trouble-shooting etc.
I'm implementing Firebase login and have made some changes...and now, after I log into my app, the 'background page' becomes the html of the current popup.
Manifest below...
When you reload or first install the Extension I see what I'm used to...and can click on the link to view the "_generated_background_page.html". Buttons in the popup correctly communicate (via messaging) to run functions in background.js.
However, after logging in, the new popup (I redirect to a new popup for logged in users) replaces the background page (my words) and (most importantly) I can't access the background page anymore. Messaging doesn't have any effect and I can't "see" the console / inspect the background.js page.
In the past I've seen a background page AND another, open page and can inspect them both.
Any thoughts on how I have succeeded in painting myself into a corner? It's as if I'm closing the background.js file.
Manifest:
{
"manifest_version": 2,
"name": "Annotate PRO for Chrome",
"short_name": "Annotate PRO",
"description": "Right-click access to a pre-written library of comments. Write it once, to perfection, and reuse forever!",
"version": "3.1.1.0",
"permissions": [
"identity",
"identity.email",
"clipboardWrite",
"clipboardRead",
"activeTab",
"tabs",
"contextMenus",
"storage",
"webNavigation"
],
"content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'",
"externally_connectable": {
"matches": ["http://*.11trees.com/*"]},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"windows": "Alt+A",
"mac": "Alt+A",
"chromeos": "Alt+A",
"linux": "Alt+A"
}
}
},
"key": "XXX",
"oauth2": {
/*"client_id": "XXX",*/
"client_id": "XXX",
"scopes": [
/*"https://www.googleapis.com/auth/chromewebstore.readonly",*/
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"
]
},
"background": {
"scripts": ["/dscripts/jquery-3.1.1.min.js","/dscripts/firebase.js","/scripts/background.js"]},
"content_security_policy": "script-src 'self' https://ssl.google-analytics.com https://apis.google.com/ https://www.gstatic.com/ https://*.firebaseio.com https://www.googleapis.com; object-src 'self'",
"content_scripts": [
{
"all_frames" : true,
"matches": ["http://*/*","https://*/*"],
"js": ["/scripts/content.js"]
}
],
"icons": {
"16": "Annotate16.png",
"48": "Annotate48.png",
"128": "Annotate128.png"
},
"browser_action": {
"default_icon": {
"19": "Annotate128.png",
"38": "Annotate128.png"
},
"default_title": "Annotate PRO for Google Chrome",
"default_popup": "aHome.html"
}
}
background.js
//URLs for scripts
var baseURL = "http://localhost/AnnotateX/Scripts/Dev/"; //local server - macOS
var xmlhttp = new XMLHttpRequest(); //why put this up front? We need a new object on each call...
//Firebase constants
var config = {
apiKey: "XXX",
authDomain: "XXX",
databaseURL: "XXX",
storageBucket: "XXX",
// messagingSenderId: "XXX"
};
firebase.initializeApp(config);
//listener for chrome start
chrome.runtime.onStartup.addListener(initApp()); //This fires verification check...
function initApp() {
// Listen for auth state changes.
// [START authstatelistener]
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
var displayName = user.displayName;
var email = user.email;
var emailVerified = user.emailVerified;
// var photoURL = user.photoURL;
// var isAnonymous = user.isAnonymous;
var uid = user.uid;
var providerData = user.providerData;
console.log('We\'re a user...coming through: ' + providerData);
if (user.emailVerified) { //Account is verified
console.log('We\'re a VERIFIED user... ' + emailVerified);
var url1 = baseURL + "aCheckUsers.php"
var url2 = "&fbUserID=" + uid + "&UserEmail=" + email + "&fullName=" + displayName;
$.ajax({
type: "POST",
url: url1,
data: url2,
dataType: 'json',
success: function(arrResult){
arrUserData = arrResult;
console.log('User data: ') + console.log(arrUserData);
localStorage.userDetails = JSON.stringify(arrUserData);
localStorage.userID = arrUserData.userID;
localStorage.licType = arrUserData.LicType;
startup();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log('Error: ' + errorThrown + ' / ' + textStatus) + console.log(jqXHR);
}
});
}
// [START_EXCLUDE]
// [END_EXCLUDE]
} else {
// Let's try to get a Google auth token programmatically.
// [START_EXCLUDE]
console.log('Not a user (background.js)');
// [END_EXCLUDE]
}
});
}
function signOut() {
console.log("Logging out via subroutine in background.js.");
firebase.auth().signOut();
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {method: "logOut"});
});
chrome.browserAction.setPopup({ //Sets popup to last visited
popup: 'aHome.html' // Open this html file within the popup.
});
}
//function that determines whether userID exists and library can be loaded or if new user must be created first
function startup(){
console.log("Starting up...");
chrome.storage.sync.get('lastSave', function(obj) {
var syncSaveTime = obj.lastSave;
var localSaveTime = localStorage.lastSync;
console.log('local: ' + localSaveTime + ' | sync: ' + syncSaveTime);
// if (localSaveTime == null || syncSaveTime >= localSaveTime){ //test
if (localSaveTime == null || syncSaveTime > localSaveTime){ //production
// console.log("Current user: " + localStorage.userID);
console.log("Local version is outdated...should run db pulll...");
pullLibrary();
} //End process for running library load if outdated or NO data locally...
else {
console.log("We've got data - skip the heavyweight pull....use local");
processLibrary(JSON.parse(localStorage.library));
}
}); //End async storage GET
} //End STARTUP
// Firebase auth popups
function googleLoginPopUp() {
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Google Access Token. You can use it to access the Google API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
console.log(errorCode + ' - ' + errorMessage);
// ...
});
} //End Google Login
function facebookLoginPopUp() {
var provider = new firebase.auth.FacebookAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Facebook Access Token. You can use it to access the Facebook API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode + ' - ' + errorMessage);
});
}
Okay...good night's sleep and some basic debugging...
Part of my background.js startup process was to hand the user off to the post-login popup page.
self.location.href='aSearch.html'; //finally open core search page
I don't understand exactly why, but this line in background.js effectively replaced background.js with the aSearch.html and aSearch.js pages...background.js became unavailable to messaging etc.
Removing the line did the trick...and you can't open a page from background.js anyway.
I'm developing a chrome extension which will add Authorization headers to the requests.
For this I used onBeforeSendHeaders, but unfortunately it isn't working for FTP requests. I have given the permission in manifest and also in background
Manifest permissions says:
"permissions": [
"webRequest",
"webRequestBlocking",
"webNavigation",
"tabs",
"cookies",
"ftp://*/*",
"*://*/*"
]
and I used onBeforeSendHeaders like this:
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) {
if(details.url == "my_url"){
details.requestHeaders.push({'name':'Authorization','value':'my_value'});
return { requestHeaders: details.requestHeaders };
}
},
{urls: ['<all_urls>','ftp://*/*']},
[ 'blocking', 'requestHeaders']
);
callback(true);
}
And also if I fail to open FTP in Chrome; is there any other way, where I could open FTP in terminal or putty using a single command (something like ssh user#host -pw pass)?
onAuthRequired is the solution to it, and worked for me:
var target = "ftp://ftpurl/";
var myCredentials = {
username: "username",
password: "pass"
};
var pendingRequests = [];
// A request has completed.
// We can stop worrying about it.
function completed(requestDetails) {
console.log("completed: " + requestDetails.requestId);
var index = pendingRequests.indexOf(requestDetails.requestId);
if (index > -1) {
pendingRequests.splice(index, 1);
}
}
function provideCredentialsSync(requestDetails) {
// If we have seen this request before, then
// assume our credentials were bad, and give up.
if (pendingRequests.indexOf(requestDetails.requestId) != -1) {
console.log("bad credentials for: " + requestDetails.requestId);
return {cancel:true};
}
pendingRequests.push(requestDetails.requestId);
console.log("providing credentials for: " + requestDetails.requestId);
return {authCredentials: myCredentials};
}
chrome.webRequest.onAuthRequired.addListener(
provideCredentialsSync,
{urls: [target]},
["blocking"]
);
chrome.webRequest.onCompleted.addListener(
completed,
{urls: [target]}
);
chrome.webRequest.onErrorOccurred.addListener(
completed,
{urls: [target]}
);
}
<script type='text/javascript'>
window.onAmazonLoginReady = function() {
amazon.Login.setClientId('-your-client-id');
};
</script>
<script type='text/javascript' src='https://static-eu.payments-amazon.com/OffAmazonPayments/uk/sandbox/lpa/js/Widgets.js'></script>
<div id="AmazonPayButton"></div>
<script type="text/javascript">
var authRequest;
OffAmazonPayments.Button("AmazonPayButton", "-emailid-", {
type: "LwA",
authorization: function() {
loginOptions = {
scope: "profile payments:widget payments:shipping_address payments:billing_address", popup: "true"
};
authRequest = amazon.Login.authorize(loginOptions, "return url");
},
onError: function(error) {
// your error handling code
}
});
could you please check and let me know what else i need to amend for retriving the amazon customer profile.
source:
https://payments.amazon.co.uk/developer
Thanks
This code is supposed to pop up a window where the user can login and authorize the scopes you've requested. It will then redirect them to the "return url" you've specified.
The code running under your return URL must be able to take the info provided in the query string attached to the URL and then fetch the profile data from Amazon on the back-end.
I believe the JavaScript you're using defaults to an Implicit grant and will return an access token good for one hour plus a refresh token which can be used to retrieve a new access token when the current one expires.
You can use a current access token to call the profile API and get the profile information.
See their developer documentation and SDKs for different languages at: https://payments.amazon.co.uk/developer/documentation
<script type="text/javascript">
var authRequest;
OffAmazonPayments.Button("AmazonPayButton", "---Your Seller ID---", {
type: "LwA",
authorization: function() {
loginOptions = { scope: "profile payments:widget payments:shipping_address payments:billing_address" };
authRequest = amazon.Login.authorize(loginOptions, function(response) {
amazon.Login.retrieveProfile(response.access_token, function(response) {
alert('Hello, ' + response.profile.Name);
alert('Your e-mail address is ' + response.profile.PrimaryEmail);
alert('Your unique ID is ' + response.profile.CustomerId);
if (window.console && window.console.log)
window.console.log(response);
window.location.href = "--Return Url--";
});
});
}
});
</script>