oidc-client-js icon indicating copy to clipboard operation
oidc-client-js copied to clipboard

UserStore should not default to sessionStorage

Open DASPRiD opened this issue 6 years ago • 21 comments

Neither the localStorage not the sessionStorage is considered safe for handling authentication tokens. If there is for instance any third party script on the website, that script would be able to extract tokens from localStorage and sessionStorage.

Thus the InMemoryWebStorage should be made the default. Many people using this library won't know better and currently default to an unsafe storage. For people who are educated about this and can take the risk would still be able to switch.

The loss of functionality should be quite minimal in this case. The only difference will occur when the user reloads the page, in which case the OIDC provider will have to be asked again directly for a new token, instead when the browser is reopened.

DASPRiD avatar Apr 17 '19 12:04 DASPRiD

Neither the localStorage not the sessionStorage is considered safe for handling authentication tokens. If there is for instance any third party script on the website, that script would be able to extract tokens from localStorage and sessionStorage.

Sure, but that's the case with any credential stored anywhere. Don't include third party JS in your app if you don't trust it.

Thus the InMemoryWebStorage should be made the default.

This would make more JS apps not work terribly well. Imagine if a user just hit refresh in the browser -- those tokens would be lost.

brockallen avatar Apr 26 '19 14:04 brockallen

This would make more JS apps not work terribly well. Imagine if a user just hit refresh in the browser -- those tokens would be lost.

How's that a problem? Silent refresh is exactly there for solving that problem.

DASPRiD avatar Apr 27 '19 02:04 DASPRiD

Sure, but that's the case with any credential stored anywhere. Don't include third party JS in your app if you don't trust it.

A namespaced object cannot leak them though.

DASPRiD avatar Apr 27 '19 02:04 DASPRiD

A namespaced object cannot leak them though.

You mean trusted types? https://developers.google.com/web/updates/2019/02/trusted-types

Or something else?

brockallen avatar Apr 27 '19 13:04 brockallen

I'm going to close. I think the default is a reasonable balance of security. If you want something stronger, there are ways to achieve.

brockallen avatar May 12 '19 21:05 brockallen

May I recommend though that you make a note in the README, so people know the recommendation at least? Especially if they embed third party scripts (Google Analytics, some javascript library from a CDN or such), that those scripts will have access to the tokens if they don't switch to memory storage.

DASPRiD avatar May 12 '19 22:05 DASPRiD

Yep, I'm totally up for that and it's a good idea to let people know/understand as much about what they're doing/getting. Would you be willing to start a PR with it? I'll reopen this w/ enhancement so we don't forget.

brockallen avatar May 13 '19 00:05 brockallen

@DASPRiD I agree with you about the preference of using InMemoryWebStorage instead of localStorage or sessionStorage, but i'm having trouble finding a way to implement the code to manage the reload of the browser.

The loss of functionality should be quite minimal in this case. The only difference will occur when the user reloads the page, in which case the OIDC provider will have to be asked again directly for a new token, instead when the browser is reopened.

If I may ask you, how, where and when can i request a new token after the page reload?

tomthebearded avatar Jun 27 '19 10:06 tomthebearded

That's quite easy, simply do the following:

userManager.signInSilent() on page load :)

DASPRiD avatar Jun 27 '19 10:06 DASPRiD

The only real downside to this is, that after the page loads, it takes a second or so for the user to show up as signed in.

DASPRiD avatar Jun 27 '19 10:06 DASPRiD

That's quite easy, simply do the following:

userManager.signInSilent() on page load :)

Thank you but i've already tried that on the load of my Vue app, the problem is that i receive the error Error: Frame window timed out

tomthebearded avatar Jun 27 '19 10:06 tomthebearded

One big downside of InMemoryWebStorage is that you are hitting the OP much more often and credentials are not shared between tabs on the same origin. So I would argue against making it default.

Documenting the security implications of session & local storage is important though.

One thing I missed in this issue, to safely use SessionStorage or LocalStorage users should not use any 3rd party/untrusted Javascript in their app or on any of the pages on the same origin, or those other pages could still leak credentials. This is easy to forget, but quite important.

sg3s avatar Oct 17 '19 10:10 sg3s

That is exactly the point, yeah. As long as you don't have any foreign JavaScript on your your domain, you should be fine. At least from your side. Whether the user has some Greasemonkey script or such running is a different story.

DASPRiD avatar Oct 17 '19 17:10 DASPRiD

I am so much confused about token security. Some are convenced that LocalStorage is not for sensitive information others are saying it is ok to do so.

in Auth0 site here they think it is not secure using LocalStorage and think the best way securing your token is by using cookies. But I think the cookies in this approach should be set from sever not javaScript.

Again I am some how lost here and not expert in this area so if any one here can explain this issues and how for example microsoft and google are protecting there token?

baselbj avatar Jan 21 '20 08:01 baselbj

@baselbj

  • It is a consideration you need to make for your specific use case
  • Storing any credential in a browser has risks associated with it, however you do it
  • There is currently no perfect solution that has both optimal usability and security

Cookies set from the server with HttpOnly flag are the most secure method, but only applicable if you can do this (in some situations you cannot). It also introduces its own set of risks and considerations if you need to somehow make the token available in frontend javascript.

In OAuth0's reasoning, they point towards server set cookies if a backend is present. In case of an SPA they suggest logging in and storing the tokens in memory. These are both the most secure methods based on the use case, but they are not necessarily the easiest to use correctly.

You can use Session or Local storage somewhat securely too however, but it requires much more due diligence to eliminate and prevent foreign code from running on your domain/origin. This is much easier said than done in the modern landscape of SPA applications however.

sg3s avatar Jan 21 '20 09:01 sg3s

Useful discussion! I've been studying the different approaches and concerns regarding token storage and I'd like to ask what do you guys think about implementing an approach similar to the one described in this article (according to the author, it was originally purposed here).

As @brockallen pointed out:

I think the default is a reasonable balance of security. If you want something stronger, there are ways to achieve.

From what I've learned, when it comes to SPA, Okta sets local storage as default, auth0 recommends memory storage, Keycloak gives session and cookie storage options. And, as @sg3s said, at the end it's all about your use case and the security/usability relation you plan to achieve. So what about oidc-client-js giving developers the option for a higher security level (assuming that the two cookies approach leads to that)?

I'd love to hear specially from @brockallen , @DASPRiD and @sg3s . Thank you guys!

rodrigocvb avatar Sep 14 '20 22:09 rodrigocvb

Both articles lean on the concept of splitting up the token and keeping part as a HttpOnly cookie and part in the FE application for authentication. The second article doesn't even mention OAuth2 / OIDC so it is a completely custom workflow. Most notable 3rd party cookies are unreliable these days so it wouldn't really be dependable for situations where frontend and backend run on different domains.

It's not a terrible strategy idea and it may address some security concerns but it does not address broader use cases. It also violates some of the separation of concerns principles imbued in OAuth2/OIDC and customizes workflows beyond what is in the specifications. I would not suggest adopting these strategies to anyone for interoperability reasons, unless it fits their specific use case and they're prepared to solve these issues themselves.

The functionality suggested also requires (mostly) server side components to be implemented, it has little overlap with the client side authentication this library is used for. If so desired there are plenty of other (server side) libraries that would allow retrofitting OAuth2 procedures to fit these specific workflows.

The oidc-client-js library should keep mostly to the spec, and not adopt novelty functionalities beyond that scope, in my opinion. But if the OIDC or OAuth2 workgroups adopt and document specific procedures around the handling and storage of credentials, it should be implemented.

sg3s avatar Sep 15 '20 12:09 sg3s

I added

userManager.signinSilent()

as suggested above and that helped somewhat but that started breaking once I logged out of one of multiple tabs, see https://github.com/Edgeryders-Participio/realities/issues/191#issuecomment-756828918

Powersource avatar Jan 08 '21 16:01 Powersource

Sure, because your session at the STS is gone, silent renew via the authorization endpoint (in the iframe) won't work.

brockallen avatar Jan 08 '21 16:01 brockallen

Yeah it's reasonable that the silent renew doesn't work in that case, but when it doesn't work (since I'm not logged in) then shouldn't it clear the user's local userData (i.e. make the user see that they're logged out clientside)?

Powersource avatar Jan 08 '21 19:01 Powersource

then shouldn't it clear the user's local userData

that's up to you. you can handle the userSignedOut event then call the user mgr's removeUser to achieve that.

brockallen avatar Jan 08 '21 22:01 brockallen