How to share local storage auth token from my site to my WebExtension?
Asked Answered
G

2

8

I have a bookmarking firefox/chrome WebExtension (using the standard popup, content and background scripts). My api servers have a /login route that returns a JSON Web Token and the web app stores that in its local storage. I have full control over the extension, api, and web apps.

The barefoot way to do this is to have users log in through the popup and save the auth token in the background.js to the WebExtension's local storage. But I would really like to only ask the user to authenticate on my site and have that same auth apply to the extension as well.

Is there a way to share the auth token? I see Pocket and a lot of others doing this, but I'm not sure how.

Goldsberry answered 15/6, 2017 at 5:31 Comment(6)
The easiest way would probably be to send the auth token as a cookie.Pebrook
You can read the site's localStorage using a content script, then copy the token in your background page localStorage via messaging.Jonajonah
@wOxxOm good approach, was unaware that the content script could access an external site's local storage. In fact, I thought local storage was more secure than that. That will entail opening a new tab with mysite.com though, but this is a worthwhile tradeoff for my use case. This is related: #3937500.Goldsberry
You can open your site in an iframe inside your background or popup, but that requires tinkering with content security policy in manifest, you can find answers that show how to do it.Jonajonah
Curious why is this not more common of an issue. Seems like many developers expect the auth state of their website to be the same in the extension.Goldsberry
@Forvin how would i do that? edit:nvm, saw the answerEnforce
L
8

You could store the token as a cookie. Cookies work like localStorage, but with the extra that the're also by default wrapped into every HTTP request to the server. And here comes the trick. Chrome Extension can gain access to HTTP requests, with the use of webRequest API. Thus it can take a peek into the request headers and know your cookies. Having that web token as a cookie makes it available to the extension.

But still you need to wait for your website to be opened by the user, to have HTTP requests flowing and ready to be peeked at, right? Not really. You can make make an ajax request directly from the extension.

Here's to illustrate how the whole thing would work:

manifest:

"permissions": [
          "webRequest",
          "webRequestBlocking",
          "*://*.my_site.com/*"
        ]

Background page:

function callback (details) {
    //
    token = func_extract_token_from_headers(details.requestHeaders);

    chrome.webRequest.onBeforeSendHeaders.removeListener(callback); 

    return {cancel: false} //     set to true to prevent the request 
                           //   from reaching  the server
}
chrome.webRequest.onBeforeSendHeaders.addListener (callback,
        {urls: ["http://www.my_site.com/*", "https://www.my_site.com/*"]},
        ["blocking", "requestHeaders"]);


var xurl = "https://www.my_site.com/";
var xhr = new XMLHttpRequest();
xhr.open("GET", xurl, true);
xhr.send();

I should mention that there's a cleaner way to do it, but it currently does not work due to CSP--Content Secutiry Policiy. Opening the website in an iframe inside the background page, as mentioned by wOxxOm in comments, should work, with added CSP whitelisting of the website. This approach would also avoid prompting the user for credentials, and would be cleaner. Unfortunately it's not currently working

EDIT:

Sorry, I was wrong about my last claim: that iframes opening external pages are blocked in background pages. To display it in the background page(or popup for that matter), simply whitelist for CSP -- shown below.

Apart from the business of opening the page in an iframe, you need to communicate with it. That should be done using the window.postMessage API

Here's to exemplify how it all should come together:

Manifest:

// Whitelist your website
    "content_security_policy": "script-src 'self' https://my_site.com/; object-src 'self'"
// Have the background declared as html
  "background": { "page": "background.html"}

Background:

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event)
{
  if(event.origin == "https://my_site.com"); //   you may want to check the 
                                             // origin to be your site 
  chrome.storage.storage.local.set({'auth_token': event.data});  // the token
}

iframe = document.createElement('iframe');
iframe.src = "https://my_site.com/";
// Have a div ready to place iframe in
document.getElementById('div').appendChild(iframe);

iframe.contentWindow.postMessage("give_token", "https://my_site.com")

The web page:

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event)
{
  if(event.origin == "your_extension_id_aeiou12345");
     event.source.postMessage(''+localStorage.auth_token, event.origin);
}

EDIT:

Also, to have the website display in an iframe, make sure the X-frame-options response header is not set to a blocking value. You can either remove it, or set it to a non-blocking value, or white-list the url of the extension.

Leninist answered 18/6, 2017 at 19:13 Comment(2)
Good approach. In my particular case, I already have the token in local storage instead of a cookie. I could start using cookies, but I think with some modification, your answer can be used for local storage too. That just depends on if the iframe in my background will allow access of local storage.Goldsberry
@Goldsberry Iframes do allow access to localStorage, but only if the iframe doesn't have a sandbox attribute specified. If you do add that attribute, make sure it has these 2 values added: <iframe sandbox="allow-scripts allow-same-origin"></iframe>Leninist
G
1

Since my requirements were as simple as "I need auth token T from website W", here's what I did:

First, store a cookie on the main website with the auth token, even if its already in local storage.

On popup script, before rendering, call

browser.cookie.get({name: 'https://website-W.com', name: 'auth_token'})
.then(function(cookie) {
     console.log(cookie); // {..., value: "bearer-token-value" }
     var token = cookie.value;
     // send message to background script to store cookie globally
     // continue rendering successfully
})
.catch(function(error) {
    console.error(error);
    // handle not logged in
})
Goldsberry answered 18/9, 2017 at 6:11 Comment(2)
Don't store auth tokens in localStorage. localStorage is unencrypted and insecure. Always use cookies for auth tokensEnforce
Note that this doesn't seem to be working in Firefox at all, while it works perfectly for chromium.Cerenkov

© 2022 - 2024 — McMap. All rights reserved.