w3c / ServiceWorker

Service Workers
https://w3c.github.io/ServiceWorker/
Other
3.63k stars 315 forks source link

Service Worker as secure storage and provider for access token and refresh token #1626

Open DenyVeyten opened 2 years ago

DenyVeyten commented 2 years ago

Web apps as public clients in OAuth2.0 need to save access and refresh tokens in a browser context.

There are different techniques to achieve this, but when we consider security and persistence (page refresh/tab reopen), there are only two options: http-only secure cookie or Service Worker.

There is performance issue with cookie where it's sent with every request to a domain that set it. Where access token is not needed sometimes and refresh token is not needed most of the time. Also, all requests to services that use that access token should be proxied via the same domain.

Service Worker approach has no such issues/limitations as app can decide explicitly when to attach token to a request. But it has two issues currently in comparison to cookie technique:

  1. In PoC from the link above token is stored in SW closure, and is lost with browser closing-opening. There could be a storage that is accessible only from SW context (I suppose CacheStorage is shared between page's window and SW, so can't be securely used as-is for this purpose).
  2. Token can't be sent (via headers modification) with initial page request (Request.mode === 'navigation'), where e.g. SSR is needed.

I think if those two issues could be solved, Service Worker approach would be the best practice to store access/refresh token.

Sorry if it looks stackoverflowy, I thought to post it in a form of feature request in case I got the aspects correctly. Would be glad to hear if these issues can be resolved by existing technologies.

hcldan commented 5 days ago

I would like to second this request.

However, I need to make some notes about the link you provided. I am focused on the service worker(SW) approach listed in that link. However, there's currently a draft document talking about the security implications of such an approach (which also rules out all of the other approaches outlined).

security background

https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-7.4 It discourages the use of SWs as OAuth clients and lists a few reasons.

These approaches are made possible if the site itself is compromised. (It's best not to make things worse when something bad happens. In this case, an attacker that has compromised an origin could generate tokens for users at will. That's much worse.)

The problem is that while the SW can handle these requests and prevent the page from making them, it will be unable to do that in a new window/frame if the SW has been unregistered by malicious code first.

However, if you take a few precautions, this won't be an issue.

  1. The IDP should use authentication cookies that are same-site: lax or better.
  2. The IDP should send headers preventing being framed (X-FRAME-OPTIONS, CSP)
  3. The IDP should send a COOP header of same-site.
  4. The server of the OAuth application should implement the redirect endpoint with a redirect removing the auth code from the url. (The SW, when installed and active, will intercept this request before a server redirect)

(1-3) are likely best-practice for any serious IDP implementation.

  1. Will prevent cookies from being sent to a "framed" IDP, preventing it from knowing who the user even is.
  2. Will prevent a "framed" IDP from rendering.
  3. Will force any popup window navigated to the IDP into a new process and break the connection it had with the opening page.
  4. Will prevent the page from spying on a window or frame to watch the url change and extract the auth_code.
    • Tested in FF and Chrome with a 1 second delay on redirecting the redirect callback request. And hammered by polling the window/frame. (ex: /callback?auth_code=asdasfasfd --1-second-delay--> 302, Location: /does_not_exist ). I was never able to read the intermediate url with the auth_code.

All of these work together to prevent the page from initiating an authorization flow and obtaining an authorization code (together with the code_validator can be used to obtain an access and possible refresh token from any machine).

the ask in this ticket

SWs do not stay active for long. Tricks to keep the tokens in scopes or globals don't work since they are reset when the SW sleeps and comes back.

You can hang state off of things like self.registration or self.serviceWorker (yuck) but those don't survive SW updates or page reloads.

What is needed is a secure, private store for things like tokens that the SW can use to persist information. The page must not have access to this storage. And it would be great if it were encrypted at rest on the filesystem.

The current options we have are all shared: indexedDB, Cache, cookieStore.