Browser Extension - maintain login auth session across multiple domains - google-chrome

Had to ask this question as I couldn't find the correct way of asking Google.
I am building a browser extension that requires the user to login using his/her credentials.
Lets keep the OAuth2 way aside for a moment there. After logging in, I am storing the JWT token received from the server in the local storage.
Now when the user navigates to another website, the extension does not have access to the stored local storage data due to cross domain access restriction.
I would like to know if there is any way to maintain the session across multiple domain. Can't ask the user to keep on logging in for every other site, he/she navigates to.
Anywhere else we can store the token to make it accessible everywhere?
Edit:
For the storage via content script, have used chrome.storage.local.
On page load, the content script sets the value from the chrome.storage.local into window.localStorage, if any.
An iframe is embedded into the web page. Display none by default. Iframe does not have any URL set.
Then User clicks on the browser Action button, the iframe is displayed.
User enter the login credentials. These are captured by the script file loaded in the head section of that iframe.
Now on submit, AJAX call is made and then on its success, stores the JWT token in the storage via window.localStorage.
Here, I also want to store the same token in the chrome.storage.local so that when the page is refresh or navigated to another domain, the Step 2 from above will execute and set the window.localStorage with the same token as the previous one has. This way, user is logged in already.
Anything wrong with the above? If not, then how to send the token from the iframe to the content or background script?

I would use a background script.
Therefore add
"background": {
"scripts": [ "background.bundle.js" ],
},
to your manifest.json.
Basically a background script is like an extra browser window instance,
just running in the background concurrent to all active sites.
Here you can find an explanation how to send messages between your active sites (content script) and the background script of your application:
https://developer.chrome.com/extensions/messaging
Explaining all of this goes beyond the scope of this answer.
Basically you are interested in the "Long-lived connections" section.
You should first establish a connection between your content script and your background script. If an event (like login) occures in the content script you can use the connection to notify the background script or
vice versa.
In your background script you have an object chrome.store.local, which basically is the same as your local storage. There you can save your JWT.
Via the message passing you can persist the JWT in the background script and access it (when you navigate) through the message system in every window, and therefore it is domain independent.
Certainly you can also check if the domain is the right one in your content script.
EDIT:
As I understand your question, your problem is to get the token out of the iframe. I am not sure how your authentication in the iframe works,
but lets assume it is a form.
Then you can access the DOM of the iframe via:
my_iframe.contentWindow.document
.getElementById("myForm")
.onsubmit = function() { /* todo */ }
If you are not the creator of the iframe you should probably wrap
the onsubmit function, by something like this:
var my_form = my_iframe.contentWindow.document
.getElementById("myForm")
var old_handler = my_form.onsubmit
my_form.onsubmit = function() {
/* todo */
if(old_handler != undefined) {
var result = old_handler.apply(this, arguments)
}
/* todo (maybe send to background) */
}
Otherwise you can just delegate the event to your background / content script. Your background script can than do the authentication.
var my_form = my_iframe.contentWindow.document
.getElementById("myForm")
.onsubmit = function() {
var result = authenticate_background(arguments)
}
Here the authenticate_background sends the arguments to the background script.
Keep in mind that you cannot send dom elements to the background script.
Therefore you first must serialize the arguments.
In general I do not like the approach of using an iframe,
but if you have a given authentication provider this could be the only
viable option. Also there might be a more elegant way than wrapping
the onsubmit function. Maybe the script running in the iframe has
a special mechanism (callback) where you can register your authentication.

Related

How can I protect json data from "XHR finished loading […]"?

Hello I'm doing a PWA and I use Google spreadsheet to store info and send emails.
When I use the app, in the console shows me the "XHR finished loading" message with an url with json data from my entire sheet.
What I want is that people don't have access to that URL or the JSON data, is this possible? or What should I do so that information is not visible in that way?
if your UI can access a URL, anyone could.
You can protect it though, deepening on your server side capability.
If your server side can authenticate the request to have proper credentials, then the protection can be implemented.
But you can't mask anything from UI to prevent users from seeing it. What goes to browser -> user have visibility. PWA wont change that fact.

Issue with Gmail addon needing dynamic auth url

We are creating a Gmail add-on that integrates with a CRM product. The product is single-tenant and every client has their own site path under our domain. Each client also has their own path to authorize with, e.g. https://example.com/siteA/oauth/authorize.
When setting up the custom auth screens, it looks like we are expected to know the url to assign to a click event on the auth button.
authButton2 = CardService.newTextButton().setText('Begin Authorization!')
.setAuthorizationAction(CardService.newAuthorizationAction()
.setAuthorizationUrl(authurl));
I would like to have some user input before this point. I was playing with an input field where the user could type their site name an then click the authorize button although I was not able to overwrite the original authurl supplied to the setAuthorizationUrl method.
Ideally it would be nice if the add-ons had a way to store some one-time input (like "site name" in this example) so we would not have to ask again, but I have not found anything like this.
This seems like we need a central auth endpoint but am trying to avoid this.
Anyone have any experience with this or any ideas?
Just build the authorization button using the proper url after you get it from the user input.
You can't modify that button once you create it. Rather than modify it, just create the button after you receive the url and reload that card. Or put the authorization button a new card that you push to the top once you receive user input on the auth url.

How do I persist login state for a user between my website and my chrome extension

I built a Chrome Extension, where a login form is displayed as a side bar using content scripts injection. I do not want the user to see this login form if the user is already logged in on the website and vice versa if the user logs in on the Chrome Extension and then visits the website, user should automatically log in.
I am returning tokens from the backend when a user successfully logs in.
My question is, what is the best way to store these tokens so both the content scripts in my extension and the website have access to the token to check to see if the user is already logged in.
As far as I understand I have localStorage, ChromeStorage but I do not know if they are shared between the tabs of the browser and the extension.
Any direction is highly appreciated.
Thank you.
If you're returning tokens, a reasonable way to do it would be to inject a content script into pages that match the callback URL containing the token, extract it and save into chrome.storage. It is shared between the content script and all other extension contexts.
Do note: chrome.storage is not exactly secure: it's not encrypted on disk, and can be snooped upon with Dev Tools. Then again, the token is normally stored in the cookie store, which can be likewise examined even without access to the (slightly) more secure password storage.
Perhaps the only more secure way to keep the token in the extension is chrome.identity API, but then you have to login separately, defeating your goal.

How to connect to the Facebook Graph API from Google Apps Script?

I'm trying to connect to the Facebook Graph API via a Google Apps Script but I'm getting an error
I've tried:
function myFunction (name) {
FB.init({
appId : '{your-app-id}',
status : true,
xfbml : true,
version : 'v2.0'
});
var jsonData = UrlFetchApp.fetch("graph.facebook.com/"; + name);
}
I've also tried:
function myFuntion(name) {
window.fbAsyncInit = function() {
FB.init({
appId : 'your-app-id',
xfbml : true,
version : 'v2.0'
});
};
var jsonData = UrlFetchApp.fetch("graph.facebook.com/"; + name);
}
but neither have worked, I always get a:
"ReferenceError: "FB" is not defined." and a "ReferenceError: "window" is not
defined"
and
"(#4) Application request limit reached","type":"OAuthException","code":4}}
despite putting in my facebook app ID into the variable. I know that "window" is part of an external javascript library so that's why I'm unable to use it in a Google Apps Script, but even after looking at other posts I'm still confused on why I get a "FB" is not defined error.
Any ideas on how to solve this?
There are error codes at the bottom of this page:
Facebook Graph API - Error codes
The "OAuthException" has to do with the Login Status. If you get that error, then you aren't logged in, and to get what you want, you need to be logged in.
You can get an App Access Token using a Server to Server request. There are four types of
Access Tokens:
User - to read, modify or write a specific person's Facebook data on their behalf.
App - modify and read the app settings, and publish Open Graph actions.
Page - read, write or modify the data belonging to a Facebook Page.
Client - the client token is used rarely. Very limited Access to Facebook.
Forms of Access Tokens
User access tokens come in two forms: short-lived tokens and long-lived tokens
short-lived - lifetime of about an hour or two - generated via web login
long-lived - lifetime of about 60 days
You probably don't have an App Access Token. You have an App ID, but that's different than an App Token.
You only get your App Token once. You need to run some code to get it.
Note, that you also must know your App Secret in order to run this code. If you don't know, or have your App Secret, then you need to get that.
See if you can run this code:
//A Facebook App Token never changes unless you go to the Facebook Developers Console,
//and you
//change the App Secret. So, do NOT keep requesting a new App Token. Just get it once,
//then
//hard code it into a backend secret function.
// The App Token can be used to modify your App, but you can just do that 'Manually'
function getOneTimeFB_AppToken() {
Logger.log("getOneTimeFB_AppToken ran");
//keep this info secret
//Generate an App Access Token
var myApp_ID = 'Your App ID';
var myAppSecret = 'Your App Secret';
var optnAppTkn = {"method" : "get"};
var getAppTknURL = "https://graph.facebook.com/oauth/access_token?client_id=" + myApp_ID + "&client_secret=" + myAppSecret + "&grant_type=client_credentials"
var getAppTkn = UrlFetchApp.fetch(getAppTknURL, optnAppTkn);
Logger.log("Object returned from GET: " + getAppTkn)
var myAppTkn = getAppTkn.getContentText();
Logger.log("myAppTkn: " + myAppTkn);
};
Run that code, then in the script editor, choose the VIEW menu, and the LOGS menu item. Read what is in the LOGS. Don't keep running this code over and over again. Just run it once if it's successful.
If that code works, then you just successfully communicated with Facebook.
You need to understand what the Tokens can do, and what your options are. If you are not going to get a token from a user through client side authorization, then you need to understand the App Token.
App Tokens allow you to interact with Facebook on behalf of an app rather than a user. This can be used to read YOUR app insights and modify the parameters of YOUR app.
You never want to use an App Token in client side (browser) code. That would be a major security problem.
However, if a user has granted your application publishing permissions, then you can use the App Token to publish content to Facebook on behalf of that person. So, app access token can be used in place of a user access token to make API calls IF your app has been granted publishing permissions.
But how do you get publishing permissions? Well, there is no way to get the initial short term access token through the server. That just makes sense if you think about it in terms of security. You can't get the initial, short term access token any other way than through a client login. So, if you want to do something that isn't within the bounds of the App Access Token, you can't do it without having the user login through client side.
You can achieve a client side login, without using the JavaScript SDK. So, in the case of an Apps Script Stand Alone HTML web app, you can still use Facebook login without needing to link to the Facebook JavaScript SDK. If you need to do that, let me know.
In that code, FB is an object. The object needs to be assigned "key/value" pairs. Every "key/value" pair is an element (property) in the object. The error is directly related to how objects work. That FB object gets assigned values from a link (inside HTML) to the Facebook API. If you are trying to use an HTML link to the Facebook API from server side (.gs) code, it won't work. There are lots of things that could be going wrong. In order to know exactly what is going wrong, we need to know whether that code is in a gs file, or an HTML file inside a <script> tag.
There are a couple of ways to connect to Facebook:
From HTML (Client Side)
From the server with HTTP Requests
It looks like the code you are using is from an example of how to use the Facebook JavaScript SDK that is meant to run from inside HTML. The problem with that, is that Apps Script sanitizes HTML sent to the browser. So, if you try to link to the Facebook JavaScript SDK through the HTML, you may not get access. I know that, in the past, I have not been able to use a link to the Facebook API in HTML with the NATIVE sandboxed mode. I haven't tried the new IFRAME sandbox mode.

How to use image of a site to display at other page when site is private

I have a Google Site which is private. I have announcement page where I do post...
I want to fetch the latest post image and want to display it at other page...
But I am getting problem that if site is private, I am not able to fetch that image from its URL and as well as with its content.
Code:
var page = SitesApp.getSite(domainName, sitename).getChildByName(announcementPageName)
var announcements = page.getAnnouncements();
var coll=[]
for(var i=0;i< announcements.length;i++){
var announcementJson={title : announcements[i].getTitle(),
content : announcements[i].getTextContent(),
image : announcements[i].getAttachments()[0].getUrl()
}
coll.push(announcementJson)
}
When I used the coll[i].image in "img src=""" tag of HTML, it did not give output.
How can I solve this?
My site is in a Google Apps Domain and the permission of site is either private or person within domain. I read somewhere that Google Sites encode the image URL, and if site is private then post tag will not be valid for encoded URL. If it is so, then what is alternative of that?
You will need to work around the security settings of the domain and google. This may violate your employment agreement and local laws, so check with your domain administrator first.
Overall concept:
In a domain account, create an apps-script with a time-based trigger. This script will periodically capture the image you are interested in, and save it somewhere. (We'll assume that somewhere is the user's google drive, and that it is synchronized onto a pc using the same domain account.)
Outside of the domain account, another time-based script will get that image from somewhere, and put it somewhere public. This could easily be a python script running on the same pc that has the google docs sync; that script can check for the presence of a new copy of the image file, and copy it to a public location (such as a Dropbox account -- see Python API). Alternatively, your company may have a public web server that you can copy the file to.
Your external (non-domain) site need only refer to the image by its somewhere public URL (e.g. Dropbox).
I hope this helps get you started.