microsoft-authentication-library-for-js icon indicating copy to clipboard operation
microsoft-authentication-library-for-js copied to clipboard

Allow to disable encryption of localStorage

Open JanStevens opened this issue 10 months ago • 5 comments

Core Library

MSAL.js (@azure/msal-browser)

Wrapper Library

MSAL React (@azure/msal-react)

Public or Confidential Client?

Public

Description

Hi,

We use msal-react in a capacitor app (mobile), we noticed that upgrading to latest msal v4 and msal-react v3 that the localStorage is now being encrypted with a session token.

This means for mobile apps that from the moment you put the app to the background, the session is removed and you have to login again which makes the app completely unusable.

I would suggest to add a setting to disable the encryption (with proper warnings / indications / remarks) specifically for this case.

Regards

JanStevens avatar Mar 04 '25 07:03 JanStevens

This is a feature by design, added for security. Why does ssoSilent not work as you should have a session in the background without prompting the user to explicitly use interaction?

sameerag avatar Mar 10 '25 15:03 sameerag

This is a feature by design, added for security. Why does ssoSilent not work as you should have a session in the background without prompting the user to explicitly use interaction?

We are using msal react in a capacitor app. This works by having a native process which serves the web app to a web view.

Everytime the app moves to the background the session is ended so all session data is removed.

If the app moves to the foreground there are tokens but they are encrypted without a session key so they have to login.

Are you implying that we should just call ssoSilent whenever the app comes to the foreground? 🤔

JanStevens avatar Mar 10 '25 21:03 JanStevens

It looks like this might be affecting our playwrigt tests - we save MSAL state to re-use in every test. It seems that with encrypted storage, we won't be able to re-use auth state to execute tests.

karpikpl avatar Mar 11 '25 19:03 karpikpl

It looks like this might be affecting our playwrigt tests - we save MSAL state to re-use in every test. It seems that with encrypted storage, we won't be able to re-use auth state to execute tests.

Dealing with the same issue in playwright tests.

Adam-Wallenrod avatar Apr 01 '25 11:04 Adam-Wallenrod

My hacky workaround using patch-package so the encryption key is also stored in local storage.

diff --git a/node_modules/@azure/msal-browser/dist/cache/LocalStorage.mjs b/node_modules/@azure/msal-browser/dist/cache/LocalStorage.mjs
index 0c0d195..35bccf2 100644
--- a/node_modules/@azure/msal-browser/dist/cache/LocalStorage.mjs
+++ b/node_modules/@azure/msal-browser/dist/cache/LocalStorage.mjs
@@ -6,7 +6,6 @@ import { base64DecToArr } from '../encode/Base64Decode.mjs';
 import { urlEncodeArr } from '../encode/Base64Encode.mjs';
 import { createBrowserAuthError } from '../error/BrowserAuthError.mjs';
 import { createBrowserConfigurationAuthError } from '../error/BrowserConfigurationAuthError.mjs';
-import { SameSiteOptions, CookieStorage } from './CookieStorage.mjs';
 import { MemoryStorage } from './MemoryStorage.mjs';
 import { getAccountKeys, getTokenKeys } from './CacheHelpers.mjs';
 import { StaticCacheKeys } from '../utils/BrowserConstants.mjs';
@@ -33,8 +32,7 @@ class LocalStorage {
     }
     async initialize(correlationId) {
         this.initialized = true;
-        const cookies = new CookieStorage();
-        const cookieString = cookies.getItem(ENCRYPTION_KEY);
+        const cookieString = this.getItem(ENCRYPTION_KEY);
         let parsedCookie = { key: "", id: "" };
         if (cookieString) {
             try {
@@ -65,10 +63,7 @@ class LocalStorage {
                 id: id,
                 key: keyStr,
             };
-            cookies.setItem(ENCRYPTION_KEY, JSON.stringify(cookieData), 0, // Expiration - 0 means cookie will be cleared at the end of the browser session
-            true, // Secure flag
-            SameSiteOptions.None // SameSite must be None to support iframed apps
-            );
+            this.setItem(ENCRYPTION_KEY, JSON.stringify(cookieData))
         }
         // Register listener for cache updates in other tabs
         this.broadcast.addEventListener("message", this.updateCache.bind(this));

IMO the encryption gives a false sense of security. Yes local storage is "encrypted" but if an attacker manages extract your local storage then they obviously can also extract javascript session cookies so you back at the start.

JanStevens avatar Apr 02 '25 14:04 JanStevens

@sameerag could you please clarify what you mean by using "ssoSilent"? Now with the session cookie encryption, the "keep me signed it" is broken completely. How can the users be automatically logged in when they open the browser?

nbelyh avatar Apr 16 '25 12:04 nbelyh

IMO the encryption gives a false sense of security. Yes local storage is "encrypted" but if an attacker manages extract your local storage then they obviously can also extract javascript session cookies so you back at the start.

You're absolutely correct this does not protect against token exfiltration (which is acknowledged in the release notes), however, this isn't the scenario encryption is intended to address. Encryption was added to ensure cached auth artifacts don't outlive the server session, which we could not promise with localStorage alone.

@sameerag could you please clarify what you mean by using "ssoSilent"? Now with the session cookie encryption, the "keep me signed it" is broken completely. How can the users be automatically logged in when they open the browser?

No changes have been made to server-side auth artifacts. "Keep me signed in" still behaves the same way it always has, the lifetime of the local app cache is the only thing that has changed here. The recommendation is to call ssoSilent when no user is signed in (e.g. getAllAccounts().length === 0) to re-authenticate silently, without the need for pre-existing cache. If the user has selected "keep me signed in" the service-side session artifacts will persist even if the app cache does not. If this call throws it means either the user did not select "Keep me signed in" or they signed out in another tab and you should invoke acquireTokenRedirect or acquireTokenPopup to prompt them to sign back in.

tnorling avatar Apr 18 '25 22:04 tnorling

I still do not see a value in the localstorage encryption. Is that trying to sync the server and the js tokens expire times? What is the real issue if a auth artifact outlives the server session?

axhaferllari avatar Apr 18 '25 22:04 axhaferllari

The 'Keep Me Signed In' : False state was not being respected prior to this change.

Id and access tokens are stateless. Once they've been issued they are still valid until their expiration even if the server session has ended. This is problematic, especially in shared device scenarios, such as public computers, because although the server session cookie is cleared when you close the browser, the next user may still have access to your emails, documents, etc using the cached tokens client-side. LocalStorage encryption essentially crypto shreds the data after the browser is closed such that if you did not select 'Keep me signed in' you are effectively signed out across client and server, as promised.

For users who did select 'Keep me signed in' they should see no change in behavior between v3 and v4 if the application is following the recommended best practice of attempting ssoSilent when no users are signed in.

tnorling avatar Apr 21 '25 20:04 tnorling

@tnorling What is problematic is an extra (seconds?) call to the server when opening the application. The ssoSilent needs to go to the server to restore stuff, right? Or am I missing the point? I also think that it would be nice to have an OPTION to disable this feature 🙁

nbelyh avatar Apr 21 '25 20:04 nbelyh

Unfortunately, there are no plans to provide an option to disable this particular feature because it was implemented for security and privacy reasons.

tnorling avatar Apr 21 '25 20:04 tnorling

@tnorling I see. Is there a way to do something about the app startup then? I mean, restoring the session with ssoSilent takes time. Is there something that can be done about it? It was instant before (if you just reopened the browser tab). Making user wait for extra seconds every time may be frustrating.

nbelyh avatar Apr 21 '25 20:04 nbelyh

I am also facing this challenge and have been trying to find a proper example that:

  • Uses Angular 19
  • and msalguards in routes
  • and ssoSilent

As far as I read here, the ssoSilent is needed to restore a login the browser is closed and opened (session closed)?

basrieter avatar Apr 21 '25 22:04 basrieter

I still don't agree, in a lot of scenarios having a required addition call at every page visit everytime is not acceptable and it total trashes the lighthouse score.

So we are stuck at an old msal version, time to move to something else.

JanStevens avatar May 05 '25 21:05 JanStevens

same here, cannot update to v4 because of that UX experience

gtteamamxx avatar Jun 09 '25 13:06 gtteamamxx

Unfortunately, there are no plans to provide an option to disable this particular feature because it was implemented for security and privacy reasons.

This is a terrible decision. Please make it configurable. I have a series of Playwright tests that modify the expiry times of the tokens in local storage specifically so I can test my UX when user sessions expire.

This change has broken my tests. You have left me with no choice but to downgrade back to v3.

samuelcm-bflexion avatar Oct 30 '25 11:10 samuelcm-bflexion

@samuelcm-bflexion You should never read or write to our storage entries directly, in tests or otherwise. Storage keys and values are not part of our public API surface and are subject to change at any time. We provide APIs to clear storage and load tokens into storage for this purpose instead. Please see our testing doc here

tnorling avatar Nov 07 '25 00:11 tnorling

@tnorling Thanks for the pointer, I was unaware of this. Looks like exactly what I need

samuelcm-bflexion avatar Nov 07 '25 08:11 samuelcm-bflexion

@tnorling the link you sent uses ".acquireTokenByUsernamePassword(...), which is deprecated. Could you updated withsomething we could use? It is sort of impossible to use auth caching with Playwright with the Msal v4 right now.

azollai avatar Dec 05 '25 06:12 azollai