aspnetcore
aspnetcore copied to clipboard
[Blazor][Wasm] Set oidc Authentication Options to Local Storage
Is there an existing issue for this?
- [X] I have searched the existing issues
Is your feature request related to a problem? Please describe the problem.
There is an existing issue for this but it is closed (#20574), I really think this feature is needed to allow the developer to choose where he wants to store. My current problem is that I choose Blazor as my frontend technology and I'm using ElectronJS as my container, I want to be able to allow the user to open multiple windows do make use of multiple monitors. Right now using SessionStorage, every time the user needs to Undock a panel into a separate window it triggers authentication again, I know it does not ask for credentials, it does it silently but it does go through the "Authorizing..." handshake before actually redirecting to the component. That is bad for user experience it is generating a new token in each new window. I tried to remediate this through Electron but it does not work, even populating the sessionStorage with the content of the main window it still goes through the authorization dance.
Describe the solution you'd like
I want to be able to set the Storage mechanism to be used, just like MsalAuthentication:
builder.Services.AddMsalAuthentication(options => { options.ProviderOptions.Cache.CacheLocation = "localStorage"; ... });
so it would be something like:
builder.Services.AddOidcAuthentication(options => { options.ProviderOptions.Cache.CacheLocation = "localStorage"; ... });
Additional context
No response
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
We would need this feature too.
Please consider this feature more carefully than this comment:
https://github.com/dotnet/aspnetcore/issues/20574#issuecomment-610545261
I don't think you should dismiss such a fundamental requirement on such a diverse platform like web. Many people have their own unique scenarios which you can't possibly account for all, so giving more options is the right away here rather than locking ppl down cause you "know better" security.
To put a concrete example where exception is needed, I'm using OIDC Provider which has disabled iFrame with error:
Refused to display 'http://localhost:8080/' in a frame because it set 'X-Frame-Options' to 'deny'.
So, I endup having to manually login on each tab, Plus of it I don't wanna call the server on each tab/new page load, Plus I'm building a internal tool where I'm perfectly happy to take risks of localStorage.
I have a customer that would also benefit from this feature for an intranet application. In a secure intranet, it seems reasonable to have this feature to make integration between applications or webview2 easier.
As a workaround, you can monkey patch the window.sessionStorage to point to the localStorage instead:
Just before including the AuthenticationService.js you can add this:
<script>
/* Monkey patch the localStorage onto the sessionStorage variable and accessor
Since oidc-client-js by default uses sessionStorage and the Blazor AuthenticationService
wrapper around it does not allow changing the configuration of that to localStorage,
and because the wrapper directly uses window.sessionStorage for its own additional data,
we have to change the global variable and the window property to point to localStorage instead.
*/
const originalSessionStorage = window.sessionStorage;
const sessionStorage = window.localStorage;
Object.defineProperty(window, 'sessionStorage', { get: () => window.localStorage });
</script>
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
Yes, this is a bit rough and when you still need to access sessionStorage you need to use originalSessionStorage instead, but it works and keeps you logged in between different browser sessions.
Bump, just because I really need this! Also thanks for the workaround but I still want to use sessionStorage for a few other things, I know I can have another workaround to make it available too but that is when things get out of control.
Bump
Thanks for contacting us.
We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
Bump
bump, this is still needed
Another bump
I would also love to see this in the authentication options. This is due to a usability request from our users, where since our application doesn't deal with sensitive data they want to be able to have a remember me option which lets us keep the session open for longer even if they close the application and reopen it. Especially for desktop apps like electron or PWAs where the user might come back and do multiple sessions in the same day but not want to log in every single time the app is opened Then we can still require authentication at least once every day or so.
@KaydenMiller - that can already be achieved. The session duration must be set on the OIDC authorization server (Entra, Okta, Keycloak, or whatever you use) and client apps should be able to update their short lived tokens with a seamless user experience for users (no redirects, etc.).
This feature request is for those that cannot make changes to the authorization server. If that is your case, you might want to elaborate on that aspect.
@gldraphael Yes, I have my tokens on the authentication server side set to a 10 hour expiration for the ID token and a 15 day expiration on the refresh token currently. The problem I have is that because the tokens are saved to session storage when the user closes the browser these tokens no longer exist and the user must log in again.
The "fix" mentioned by gingters above nearly perfectly fixes the issue but its not exactly what I would call a clean solution given I'm overriding the session storage to be local storage which could cause problems or unintended consequences for other systems that are expected to use session storage.
Maybe I'm just missing something in the documentation that explains how this is supposed to work but I haven't seen anything about that yet save maybe creating my own authorization handler which seems overkill since I'm not really doing anything I would consider custom.
Bump. We need this for our intranet
@KaydenMiller - I went ahead and put together an example here: https://github.com/gldraphael/blazor-wasm-oidc-sample
@gldraphael - I appreciate the example but that example has the same issue I'm trying to fix in it. If you close the browser (not just the tab) the user's session is gone and the user will need to log in again. So that effectively reproduces the issue I'm talking about.
I need a way to keep the user session authenticated even if they close the browser completely.
@KaydenMiller - oh well, I forgot to turn on "Remember me" within keycloak to make it persist the login cookie. I've updated my repo.
git pull
docker compose down # remember to do this to recreate the db from scratch, so you don't end up using the old realm file
docker compose up
I also recorded a video to demonstrate this:
https://github.com/user-attachments/assets/41cfe66a-c406-4cf8-8561-05bd5a78cd4b
This is basically the point I made in my first comment: these things are meant to be configured on the authorization server (Keycloak in this case) and not on the public client. If you are not able to do so for whatever reason, you might want to share more information about your specific use-case.
The MSAL package already supports this by allowing you to set the CacheLocation.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.authentication.webassembly.msal.models.msalcacheoptions.cachelocation?view=aspnetcore-9.0
I am not sure why the OIDC library can't just do the same thing.
The issue is pretty straight forward as stated when I opened, add the same support that MSAL package has, let us decide what we want instead of deciding for us.
Absolutely! I'm not arguing with the original issue. I was merely responding to a comment that implied that users cannot stay logged in when the browser is closed (which is not the original issue described).
My apologies that did work after the change to the realm settings. I guess I need to figure out what needs to change on Auth0's side of things. Thanks for the help @gldraphael sorry for adding extra clutter to the ticket that wasn't needed.
Thank you @gingters for the workaround. I ran into some issues with overwriting session storage and came up with this alternate workaround which just involves copying the data from local to session (on startup) and from session to local (when the app is hidden). Same as the other workaround, include the following before AuthenticationService.js. You may also need to change your tokenKeyPrefix based on your provider.
/*
Since oidc-client-js by default uses sessionStorage and the Blazor AuthenticationService
wrapper around it does not allow changing the configuration of that to localStorage,
and because the wrapper directly uses window.sessionStorage for its own additional data,
we have to copy data from session to local and back.
See https://github.com/dotnet/aspnetcore/issues/40757
*/
const tokenKeyPrefix = "oidc.user";
// copy local to session
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(tokenKeyPrefix)) {
sessionStorage.setItem(key, localStorage.getItem(key));
}
}
// copy session to local on app hidden
document.onvisibilitychange = () => {
if (document.visibilityState === "hidden") {
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
if (key.startsWith(tokenKeyPrefix)) {
localStorage.setItem(key, sessionStorage.getItem(key));
}
}
}
};