Sharing sessionStorage between tabs for secure multi-tab authentication
tl;dr; I’ve created mechanism that will leverage the secure nature of the browser sessionStorage or memoryStorage for authentication and will still allow the…
tl;dr;
I've created mechanism that will leverage the secure nature of the browser sessionStorage or memoryStorage for authentication and will still allow the user to open multiple tabs without having to re-login every time.
A refresher about relevant browser storage mechanism
- localStorage ~5MB, saved for infinity or until the user manually deletes it.
- sessionStorage ~5MB, saved for the life of the current tab
- cookie ~4KB, can be saved up to infinity
- session cookie ~4KB, deleted when the user closes the browser (not always deleted)
Safe session-token caching
When dealing with critical platforms it is expected that the session is ended when the user closes the tab.
In order to support that, one should never use cookies to store any sensitive data like authentication tokens. Even session-cookies will not suffice since it'll continue to live after closing the tab and even after completely closing the browser.
(We should anyway consider to not use cookies since these have other problems that are need to be dealt with, i.e. CSRF.)
This leaves us with saving the token in the memory or in the sessionStorage. The benefit of the sessionStorage is that it'll persist across different pages and browser refreshes. Hence the user may navigate to different pages and/or refresh the page and still remain logged-in.
Good. We save the token in the sessionStorage, send it as an header with every request to the server in order to authenticate the user. When the user closes the tab – it's gone.
But what about multiple tabs?
It is pretty common even in single page application that the user will want to use multiple tabs. The afordmentioned security enahncment of saving the token in the sessionStorage will create some bad UX in the form of requesting the user to re-login with every tab he opens. Right, sessionStorage is not shared across tabs.
Share sessionStorage between tabs using localStorage events
The way I solved it is by using localStorage events.
When a user opens a new tab, we first ask any other tab that is opened if he already have the sessionStorage for us. If any other tab is opened it'll send us the sessionStorage through localStorage event, we'll duplicate that into the sessionStorage.
The sessionStorage data will not stay in the localStorage, not even for 1 millisecond as it being deleted in the same call. The data is shared through the event payload and not the localStorage itself.
Click to "Set the sessionStorage" than open multiple tabs to see the sessionStorage is shared.
Almost perfect
We now have what is probably the most secure way to cache session tokens in the browser and without compromising the multiple tabs user-experience. In this way when the user closes the tab he knows for sure that the session is gone. Or is it?!
Both Chrome and Firefox will revive the sessionStorage when the user selects "Reopen closed tab" and "Undo close tab" respectively.
Damn it!
Safari does it right and don't restore the sessionStorage (tested only with these 3 browsers)
For the user the only way to be completely sure that the sessionStorage is really gone is to reopen the same website directly and without the "reopen closed tab" feature.
That until Chrome and Firefox will resolve this bug. (my hunch tells me that they will call it a "feature")
Even with this bug, using the sessionStorage is still safer than session-cookie or any other alternative. If we'll want to make it perfect we'll need to implement the same mechanism using memory instead of sessionStorage. (onbeforeunload and alike can work too, but won't be as reliable and will clear also on refresh. window.name is almost good , but it's too old and has no cross-domain protection)
Sharing memoryStorage between tabs for secure multi-tab authentication
So… this will be the only real safe way to keep an authentication token in a browser session and will allow the user to open multiple tabs without having to re-login
Close the tab and the session is gone – for real this time.
The downsides is that when having only one tab, a browser refresh will force the user to re-login. Security comes with a price, obviously this is not recommended for any type of system.
Set the memoryStorage and open multiple tabs to see it shared between them. Close all related tabs and the token is gone forever (memoryStorage is just a javascript object)
**
P.S.** Needless to say that session management and expiration should be handled on the server side as well.
Related Posts
Comments (35)
Imported from the original blog
Do you have code samples for you demos?
Hi Kinnard,
Right click to view source, it's a pretty simple code.
memoryStorage object is not available i window, could you please explain
memoryStorage is just a javascript object I put globally.
i.e.
window.memoryStorage = {}
Excellent explanation and demo! Would you mind sharing what adding IE support would look like?
Nevermind, I saw an example of it from the answer from which I got the link to this article originally: http://stackoverflow.com/a/...
How is this secure? If another tab can ask for the token and it will freely be given to it, what is to stop a malicious site from asking for and then using the token?
Hi will,
It's another tab from the same-origin. I.e only http://example.com/page1.html can communicate with http://example.com/page2.html.
Other websites cannot get the token.
This is default security of every browser and it's not related to this solution.
Maybe not Will's original point, but there's something to be said even for same-origin scenarios.
If you are using sessionStorage+ storage event+localStorage, it's hardly more secure than just localStorage. I guess, we can rest assured the data might be wiped from users hard drive, but this just seems like fools gold, buying a thicker bike lock, or putting a 2nd deadbolt lock on your door. I guess, nice to have, but it's a very trivial benefit imo...
If using localStorage, when page initializes, before any third party script runs, you could wipe certain localStorage values if the user is not logged in. Probably, your app code should use a wrapper, which uses JSON.stringify to store all potentially sensitive data in one localStorage key. Then, when page loads, if you are not logged in, localStorage data will be wiped.
> To avoid stalling page load, this top level code could 1. Stash your app's localStorage data in a local variable. 2. Wipe localStorage, 3. Submit request to check if user is still logged in 4. If request is successful, restore the localStorage data from the local variable.
> Your wrapper library may need a "storage initialized" or "session still authenticated" event, or all the `appStorage.getItem('token')` calls need to be async: `await appStorage.getItem('token')`.
Perhaps, if chrome stores the data unencrypted on disk, then that's ridiculous, but, it seems like a Chrome's problem, allowing localStorage/sessionStorage data to be stored on disk un-encrypted.
Also, with localStorage, it wouldn't hurt to also encrypt it ourselves, see: https://www.npmjs.com/searc...
Nice idea. Another possible alternative to using localStorage events for cross-tab communication might be to use the new Broadcast Channel API (as of Chrome 54; excludes Safari & IE).
e.g. (this example still uses sessionStorage to store the session token)
// Connect to the broadcast channel
// (the first tab to connect automatically creates the channel)
const authChannel = new BroadcastChannel("auth");
// Notify other tabs
authChannel.postMessage({message: "NEW_TAB"});
// Listen for channel messages
authChannel.onMessage = event => {
// Lookup existing auth key for the current tab (if any)
const key = window.sessionStorage.get("authKey");
switch (event.data.message) {
// A new tab has opened
case "NEW_TAB":
// If the current tab has an auth key, broadcast it
if (key) {
authChannel.postMessage({message: "AUTH_KEY", key});
}
break;
// An auth key has been broadcast
case "AUTH_KEY":
// If the current tab doesn't yet have an auth key, store it
if (!key) {
window.sessionStorage.set("authKey", event.data.key);
}
break;
}
};
This seems a little cleaner than setting then immediately deleting a localStorage key just for the purposes of triggering an event.
Thanks for the comment Scott, nice usage of BroadcastChannel.
I take your example and changed the token for the current time.
I changed this:
sessionStorage.setItem('token', '123456789');
By this:
var d = new Date();
var n = d.getTime();
sessionStorage.setItem('token', n);
But when I open multiples tabs and click on 'Set session storage' this value not updated the other tabs. Why? This is not the expected behavior?
I read this article https://blog.guya.net/2015/... and I think that 'the-never-ending-browser-sessions' throws all away on 'sharing-sessionstorage-between-tabs-for-secure-multi-tab-authentication' I'm right?
Hi Victor,
For the demonstration I only sync the sessionStorage once when the tab loads. If you want it to be synced between tabs even after they are already opened you can simply call this right after you change the sessionStorage.
localStorage.setItem('sessionStorage', JSON.stringify(sessionStorage));
localStorage.removeItem('sessionStorage');
"Opening a page in a new tab or window will cause a new session to be initiated"
By https://developer.mozilla.o...
that opinion deserve this?
Hi, The second one is really nice. But there is a weakness in comparison of the first one. In the second one there is a bad scenario: Open the demo, Set the key, Open it in new tab (key is there), Close the second tab, Refresh the first tab, Token is gone!!! Any idea? (this does not happen in the first one)
The first one uses the sessionStorage which has some weekness - it's not really gone when closing the tab.
The second one is just using a JavaScript object in memory which is definitely gone when closing the tab, but has this UX annoyance you just mentioned.
Second one should be used only when extreme security is needed.
I think using only localStorage is good. When user check "remember me", just store the token in localStorage (and token's `exp` must be a big amount of time).
But when he didn't check, define another localStorage key, and set it to the expiration time. (simulating sessionStorage). From next login, localStorage is expired. (Note: you may think the expiration of localStorage can be edited in the client, but the token has the expiration it self and will handle in server side.)
Yeah but, even if you set the expiration for a short minute that still a whole minute for an attacker to reopen the tab and be logged-in as you.
We want to use sessionStorage to be more confidant that the data is lost when the user closes the tab.
It all depends on your specific needs and the level of security you require.
I think it's definitely a feature that sessionStorage is restored upon reopening closed tabs (CTRL+SHIFT+T). This is really 'undo' for accidentally closing a tab, so there's no reason why state should not be restored also.
The standard has a relevant note supporting this at https://html.spec.whatwg.or... :
"The lifetime of a browsing context can be unrelated to the lifetime of the actual user agent process itself, as the user agent may support resuming sessions after a restart."
> the only way to be completely sure that the sessionStorage is really gone is to reopen the same website directly and without the “reopen closed tab” feature
Even after doing that, if the user now does CTRL+SHIFT+TAB, the prior tab will be opened with prior session state restored (which is still fine by me).
To achieve what you want (expunging your secure token), I think you need to catch the onunload event and expunge it there. (Please don't lobby browser makers to break the very useful 'undo' feature of restoring full session state!)
An additional factor that you might want to mention is that localStorage is not shared between http & https versions of your site; probably this is a feature for your usecase (but is a real annoyance for what I'm using localStorage/sessionStorage for).
Hi EoghanM,
Thanx for your comment.
Sometimes there is tradeoff between security and user experience.
In this case I believe that security is winning and indeed this is a bug and not a feature.
Not everything should have an undo button. You don't have an undo button after you logged-out, for example. You'll have to login again.
Same way, it's nice that you can undo a closed tab, but the sessionStorage should be cleared.
Regarding not sharing localStorage between http and https - that's a very important security feature. A simple attack example would be a MITM easily stealing all of your users precious locally stored data.
I suggest you to go with https only to mitigate any development issues you have with this feature. You'll also have the benefit of being generally more secure and ranking higher in google.
-- Guy
Thanks Guy. (Unfortunately I'm writing js for use on 3rd party websites for which I've no control over whether they use http or https or both, but you are right in that there is MITM problem. For now we have to use cookies which already cross http/https.)
I think the problem here is that you are seeing sessionStorage from one very particular perspective - storing a security token tied to login - but you seem to be failing to see that sessionStorage is useful for thousands of other use-cases not related to the separate concept of a 'logged in' session.
Clicking a logout button is much more of a deliberate act than closing a browser window/tab which happens accidentally all the time. Maybe the browser vendors should prevent restoring tabs after a certain amount of time (although there's been times when my computer has crashed or forced a restart and I've turned it off overnight in disgust, but been thankful to be able to restore tabs in the morning)
Another point would be, what if the user never closes the tab? Is it valid to leave a secure session open indefinitely? I think automatically expiring your secure token after a certain amount of time could cover all these problems, what do you think?
Obviously it all depend on the kind of system you're building.
Some highly secure systems do what you mentioned and invalidate the session after a certain period of inactivity. That's probably too much for most systems, websites and/or apps.
Also, reopening the tab and staying logged-in is probably the best approach for most websites.
My issue is that when you want to do it the other way, when you need an highly secure system. Meaning - to not stay logged-in and to clear everything when the user closes the tab, it's not that easy and not working as expected.
Can we have the solution for the same code to work in ie 11 and edge
hi, can i have the source code for the second implementation for IE too. im not that nerdy that able to write my own code. please?
Good. We save the token in the sessionStorage, send it as an header with every request to the server in order to authenticate the user. When the user closes the tab – it’s gone.
How can I send the token as a header with every request to the server. The cookie is automatically sent in the header but I have no idea how I would send the token saved in sessionStorage automatically in the header like a cookie would.
Use an interceptor. Add the data in the session storage to the header in the interceptor
Hi,
Neither the localstorage nor the memory storage code worked for my angular application. Completely stuck on this from last few days. I think I need to change it to cookie.
same problem. What exactly to be done here?
https://github.com/AdithyaP...
Please refer this POC i creadted based on this blog. Please reach out on git hub if you need any clarifications
Hey There,
wonderful Article!
We had the same Problem to have Session Storage to be shared between Tabs.
With your solution - Restoring the Session Storage at page load - i had a problem: you could never be sure if there is a session coming or not.
While bootstrapping our Angular Application, the Browser was under such a heavy load, that the response from the other tab could take up to 2 seconds.
Our application would then do an automatic certificate login due to the session storage arriving too late.
So i came up with an alternative:
Counting the open tabs and on window.onUnload i would clear localStorage if the tab count is zero!
So i would use localStorage to behave like sessionStorage except there is Multi Tab Sharing.
Even restoring of the session is not possible!
How do I count the tabs?
There is no native support to achieve this and i was Inspired by your solution of using LocalStorage for multi Tab Communication.
On Page Load:
let tabs=localStorage.getItem('tabs')
const tabId = Math.round((new Date()).getTime());
tabs.push(this.tabId);
localStorage.setItem('tabs', JSON.stringify(tabs));
OnPageUnload:
let tabs=localStorage.getItem('tabs')
tabs = tabs.filter(item => item !== this.tabId); //Deleting current tab
if (tabs.length === 0) {
localStorage.clear();
console.log('Cleared Local Storage');
} else {
localStorage.setItem('tabs', JSON.stringify(tabs));
console.log('Deleted' + this.tabId, tabs);
}
This technique as the advantage that application startup has no delay.
Maybe there is a security concern when the browser cant execute window.onUnload
Do you see any other problems?
But does not work when a single tab is refreshed, right?
Thanks for this solution. However I think your claim that the data will never be in LocalStorage is incorrect. Yes, due to Javascript's run-to-completion event-loop no other function in that same tab will be able to access the LocalStorage item, because it is deleted in the same function call. But another tab can access the LocalStorage item (though the timing is very improbable) between the setItem and removeItem. Try setting a breakpoint on the removeItem. It's not really a risk though, same application and all. But on a slow browser, something might be in LocalStorage for 1 ms after all.
Neither of the two solutions work for the following situation:
- Open demo in 3 tabs
- Clear session in one of them - it never actually clears session storage, unless it's the only tab that's open.
Which is actually very insecure - the user will have completed the action to log out, but because of the other 2 open tabs - they will still be logged in.
In that case you should put a logout function that cleans the storage, either sessionStorage or localStorage
If the goal of this solution is to be used for an authentication token, or in other words, knowing if the user is currently authenticated, a typical web site will have the following flow:
1. User navigates to web site.
2. Web site detects whether user is currently logged in.
3. If user isn't logged in, redirect to login page.
So, for step 1, the user opens a brand new web browser with one tab and navigates to the site.
Step 2, the page loads and executes your code. But there is nothing in your code that can definitively determine that no prior session data exists. You make a call to 'localStorage.setItem()' and then wire up an event, expecting to "hear" a response back. The response to the event is an asynchronous operation. You don't know how long it's going to take, or if it's ever going to even happen.
Because the event is asynchronous, and you may not get a callback, you'd have to implement some kind of timeout. But how long should you wait? A lot of times, the initial page load, or some of the data displayed on the page will be dependent upon whether the user is currently logged in. Therefore, you have to hold up the rest of the page load while you're trying to determine/wait for a response to your storage event.
I don't see this as being a very robust solution to this particular problem of storing and sharing a session identifier.
Also note that from OWASP themselves, they recommend NOT to store session identifying information in local or session storage. Instead they recommend using a cookie marked with HttpOnly.
See https://pupuweb.com/owasp-html5-security-cheat-sheet-guide/
Do not store session identifiers in local storage as the data is always accesible by JavaScript. Cookies can mitigate this risk using the httpOnly flag.