javascriptauthenticationoauthsingle-page-applicationpkce

Login out user over multiple subdomains with frontend PKCE


We have various SPAs (single page javascript apps) that run on subdomains. E.g. sub1.domain_name.com and sub2.domain_name.com.

For OAuth we were using the Backend For Frontend Architecture where the backend that served the SPA was protected with a confidential client. Cookies with the access and refresh tokens were stored in the frontend on the domain leven (.domain_name.com). When an access token was expired the protected server used the refresh token to renew the tokens and store them in the cookies. When a user logged out we had an application that terminated the session at the authorization server and removed the domain cookie. SPAs polled the backend to see if they could still access it, and if not (because user was logged out and cookie was removed on another subdomain) they refreshed the page and thus were logged out and navigated to the authorization server.

Now because it seemed more lightweight and possibly more secure we switched to an approach with Browser-based OAuth 2.0 clients, where OAuth is initiated in the SPA using a public client and PKCE. To prevent having to login on a page refresh, we store the tokens in sessionStorage and PKCE login information in localStorage. Using the refresh token the frontend refreshes the access token periodically, and for backend calls the access tokens are passed not in the cookie, but in the Authorization header.

The latter works fine for logging in, only we don't know how to properly logout on all subdomains. We can remove the localStorage and sessionStorage and navigate to logout from one subdomain, but the difficulty is that sessionStorage and localStorage is not shared over a domain (domain_name.com), but only accessible per subdomain. Thus when navigating to another subdomain the user is still logged in.

How can we logout a user that logs out on one subdomain, also from all other subdomains?

We have tried using sysend.js to connect the localStorage and sessionStorage of all tabs and windows on the same domain (via iframes). This works, but it only works for tabs and windows that are currently open. It does not work for pages that were previously visited, and are now closed (e.g. you navigate to sub1.domain_name.com and then sub2.domain_name.com, you logout on sub2.domain_name.com and are still logged in when you navigate back to sub1.domain_name.com).

Another idea is to also store a javascript accessible cookie with the current logged in userid. Then use the browser's Page Visibility API on pageload to check if the userid that is logged in matches the one in sessionStorage, and if not, remove the sessionStorage and localStorage and refresh the page, so that the user is redirected to the authorization server (where it hopefully is still logged in).

I would like to know if the latter is a viable approach. Or is there is a better solution for implementing logout in SPAs over subdomains? Or is actually the previously used Backend For Frontend Architecture with a logout service that removes the domain cookie, a better approach for this use case?


Solution

  • As is often the case with security, the solution you choose depends on the sensitivity of the data.

    MOST SECURE OPTION

    Section 6.1.4.3 of the document you reference explains how a BFF is the highest security option but also the most complex option. It deals best with token interception and exfiltration threats.

    So it is recommended if you deal with sensitive (eg financial) data. You have access to more flows, eg Pushed Authorization Requests etc.

    MULTIPLE APPS

    If you want to share web security across apps you will find that your options are closely related to deployment and URLs.

    OPTION 1: SUBDOMAINS

    Cookies can be shared, and shared browser storage requires more complex workarounds, as you are finding:

    You may need to use techniques like polling to detect logouts in another app. Or you can just wait for the next API request to fail. But you can't easily overcome the browser default bahaviour.

    OPTION 2: PATHS

    Some micro-frontend deployments use paths. Both cookies and browser storage can then be shared:

    Eg you could set a boolean flag in local storage and update it when you logout from one app. Then listen to the window.onstorage event in other apps and react immediately.

    THREATS

    As well as technical considerations, also think about threats, eg if app1 is a low security app and app2 is a high security app:

    BUSINESS AREAS

    My personal preference is to design web domains based on the business area. Then use micro-frontends within each web domain if required:

    This perhaps gives you best choices: