-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow only main script to silently log back in after a page refresh #84
Comments
Now that I'm thinking about this, suppose a malicious script manages to run before the page's main script runs. So I think what we're trying to protect against here is malicious scripts that run after the main script. |
Suppose the main script runs, uses refresh token 1 to get to a logged-in state, obtains refresh token 2, and stores it in localStorage. A malicious script running after the main script could use refresh token 2, and the main app would then detect this as soon as it tries to use refresh token 2 itself (whether after some time, or after a page refresh). But now suppose the malicious script uses refresh token 2 to silently get itself logged in, then obtains refresh token 3, and stores that in the place where the main script expects it. Then it would still go undetected by the main script. The IDP could still show the user "it looks like you refreshed the page at 13:52 CEST", and if the user didn't do that, then the user could detect that it must have been some malicious script that did it. So maybe the main script should keep a copy of the contents of localStorage inside its closure, and check once every 10 seconds whether it still matches. If it doesn't, it knows some other script is trying to trick it. I didn't find a discussion of these additional details in https://tools.ietf.org/html/draft-ietf-oauth-browser-based-apps-05 which may just mean that there is some other mechanism related to refresh tokens that I don't know about and that already covers this. :) |
I would sincerely hope that no one is storing any sort of token in localStorage. It is a little like writing your password on a sticky note and attaching it to your monitor in a shared workspace. For browser-based applications, I'm honestly not convinced that "offline" scope (i.e. refresh tokens) is the best approach. The login flow of every identity provider I've ever encountered uses cookies and so after first passing through a login flow, any subsequent flow will be transparent to a user. Why wouldn't the browser code just direct the user through the authorization code flow in order to get a new token? |
I'd like to consider relying on non-extractable WebCrypto instead of using refresh tokens. Together with Self-Issued Assertion and JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants we might not need refresh token grant all together. Client would use the same key it uses for DPoP. |
Because if the app's main script can use the IDP cookie to get back in after a page refresh, then so can a malicious script. If a script has access to localStorage then it also has access to such cookies.
Would that survive a page refresh? I think our best bet would have to be something combining:
|
Yes, non-extractable key can be stored in IndexedDB thanks to the structured clone algorithm |
Ah cool. It still wouldn't resolve the goal of keeping them available only to the main script on a page, but that definitely helps, because it will stop the malicious script from taking the keys to elsewhere, rather than using them in XHR from the page. I'll rename this issue to describe the goal rather than a part of a proposed solution. I don't intend to work on this myself, so unless/until anyone else does, maybe we should just "park" this topic as unsolved. |
Just to make this explicit, an on-server malicious script would still get full access to whatever the app itself is allowed to do on the pod. But we consider it more likely that a malicious script would exist inside the in-browser part of a Solid app than that it would exist in the backend part (if one exists). If the main in-browser script manages to load first, before any malicious scripts, after a page refresh, then it can protect its API access using e.g. a xsrf token, which would be sort of equivalent to the partial protection that refresh token rotation provides (protecting the pod access token instead of the API access token, but to the same effect). But if the malicious script comes first after a page load then the backend would have no way of knowing whether an incoming API interaction is malicious or not. So if the backend has generic powerful methods like 'runArbitraryHttpRequest', then the separation between in-browser and on-server would not provide any real security barrier. An in-browser malicious script would be able to use the backend's generic API calls do anything, e.g. to wipe the pod. Therefore, we say the risk of malicious scripts inside a Solid app mainly affects JavaScript apps without a backend, even though in reality the boundaries are more subtle. |
Cookies on the pod server's API would also work, I guess, as @jaxoncreed proposed in https://gitter.im/solid/solidos?at=5f9c1995f2fd4f60fc42b95d |
@jaxoncreed regarding your cookie-on-RS proposal, I just realised that if app1.com only accesses pod.com, there will only be a cookie session established on pod.com, and not on e.g. friend.com. So if then the page is refreshed, and then the app wants to access data from friend.com after the refresh, it will still fail. That's why I said the cookie should be on the IDP, not on the RS. If the IDP sets a HttpOnly cookie then the user will be silently logged back in to their identity for the whole web after a page refresh, not just to their own pod. What do you think? |
Consider an in-browser Solid app with no backend, where tokens are stored in a closure (in the way ISCAJ proposes, so that if other malicious scripts would be present on the page, they would not get access to those tokens.
After a page refresh, you want the app to be able to use the refresh token to silently get back into logged-in mode otherwise the UX would get very annoying.
With Refresh Token Rotation, after a page refresh, it would still be possible that a malicious script on the page gets to logged-in state instead of the app itself, but then at least you know that only one script on the page will be able to use the refresh token to silently get back into logged-in mode.
Then if the app itself is unable to, you can at least detect that the token was stolen. Smart! Would it be worth requiring support for this from Solid IDPs?
The text was updated successfully, but these errors were encountered: