Blazor Server - How To Store Encrypted Session Data In The Browser

Introduction

 
While developing for the web in front-end JavaScript, we have access to localStorage and sessionStorage, which are key-value data stores that exist within the user's web browser. These data stores are sandboxed to each website, meaning every website only has access to its own data store, and cannot access localStorage and sessionStorage objects being saved from another website.
 
The data in these data stores is considered to be volatile, meaning it is not permanent storage and it's never guaranteed to be there. The browser can decide to delete this data or the user can use a PC cleanup tool and all the stored data will be gone.
 

What's the difference between localStorage and sessionStorage?

 
Luckily they are easy to differentiate.
  • sessionStorage data only exists while the browser tab, or session, stays open. Once the user closes the tab or exits their web browser, your sessionStorage data is cleared.
  • localStorage data will persist until cleared by the browser or from some user action. 

Security implications of using browser storage 

 
It is important to note that even though this data is sandboxed, storing sensitive user data in the browser can lead to many vulnerabilities, especially if your website is the victim of an XSS (Cross-Site-Scripting) attack. For example, if your website includes a third-party script like this,
  1. <script src="https://someawesomewebsite.com/someawesomelibrary.js"></script>  
and if that website is compromised, an attacker can potentially insert malicious JavaScript into the library that will retrieve data from localStorage and sessionStorage and send it to their server! Yikes.
 
Since the security implications of using localStorage are debated, I will focus on the use of sessionStorage in this article. Session storage can be considered somewhat safer, keeping in mind the data is deleted when the session ends.
 
This is just a friendly reminder to be mindful about the kind of data you store in the web browser. 
 

How can we use sessionStorage in Blazor Server?

 
With the release of .NET 5, there have been many additions to Blazor Server and Blazor WebAssembly. With these additions we now have access to new classes, such as
  • ProtectedLocalStorage
  • ProtectedSessionStorage
These classes give us access to the client-side sessionStorage and localStorage from within our server-side C# code! Not only is this fact great by itself, these classes are also very easy to use, and they encrypt the data instead of storing it as plaintext.
 
Note
Although the data is encrypted, it isn't immune to the potential security risks mentioned before.
 
To begin using these classes, we must first import the class (either in our _Imports.razor file or in the component itself.)
  1. @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;  
And add the following line at the top of your Blazor Component,
  1. @inject ProtectedSessionStorage storage  
This will tell the Dependency Injection service to give us a ProtectedSessionStorage instance.
 
Then to set or get data,
  1. // Set   
  2. await storage.SetAsync("greeting""Hello, World!");  
  3.   
  4. // Get  
  5. var greeting = await storage.GetAsync<string>("greeting");  
 It really is that simple! 
 
Example -  Breaking News control
 
Let's say in our web app we want to have a news alert appear at the top of the page with any breaking news. The user can dismiss this banner, and if it's dismissed, it won't appear again during this session. This is a perfect use-case for session storage because we want the banner to stay dismissed until the next time the user opens your web app. 
 
First, create a new Blazor Component named NewsAlert and type the following code,
  1. @if (showAlert)  
  2. {  
  3.     <div class="alert alert-primary alert-dismissible fade show" role="alert">  
  4.         @Text  
  5.         <button type="button" class="close" data-dismiss="alert" aria-label="Close" @onclick="OnDismiss">  
  6.             <span aria-hidden="true">×</span>  
  7.         </button>  
  8.     </div>  
  9. }  
  10.   
  11. @code {  
  12.     private bool showAlert = false;  
  13.   
  14.     [Parameter]  
  15.     public string Text { getset; } = string.Empty;  
  16.   
  17.     private async Task OnDismiss()  
  18.     {  
  19.         showAlert = false;  
  20.     }  
  21. }  
 This will be the basis for our component. To make this function to our requirements we need,
  • Whenever the component is loaded, check sessionStorage to see if the alert has ever been dismissed. If it hasn't, then show it
  • When the user dismisses the alert, we need to store that data in sessionStorage for the next time the component is loaded during this session.
To check sessionStorage after the component has been loaded, we can write,
  1. protected override async Task OnAfterRenderAsync(bool firstRender)  
  2. {  
  3.     var result = await storage.GetAsync<bool>("NewsAlert.showAlert");  
  4.           
  5.     bool oldValue = showAlert;  
  6.           
  7.     showAlert = result.Success ? result.Value : true;  
  8.           
  9.     if (showAlert != oldValue)  
  10.     {  
  11.         StateHasChanged();  
  12.     }  
  13. }  
Let's break down this method.
  • First, we're asking for a bool called NewsAlert.showAlert in sessionStorage.
  • Next we're keeping a copy of the showAlert value so that later we can check if it actually changed.
  • Then we set the value of showAlert. If there is a value with that key, then set it to that value. Otherwise, it defaults to true.
  • Finally, if the value has changed, invoke the StateHasChanged() method, which tells Blazor we've changed the component state.

Note
Using StateHasChanged() can potentially force the entire component to redraw, so be mindful about its usage. Typically you should never explicitly call this method, but since we're changing the state immediately after the component is rendered, it won't update the state unless we invoke it. You might be asking why we don't use OnInitializedAsync instead, to avoid this issue? This is because Blazor seemingly doesn't like it when we do JS interop inside OnInitializedAsync(). There is a special runtime error when you do it, that says to use OnAfterRender instead.

Now finally we need to update sessionStorage when the user dismisses the alert, 
  1. private async Task OnDismiss()  
  2. {  
  3.     showAlert = false;  
  4.     await storage.SetAsync("NewsAlert.showAlert"false);  
  5. }  
The finished component
  1. @inject ProtectedSessionStorage storage  
  2.    
  3. @if (showAlert)  
  4. {  
  5.     <div class="alert alert-primary alert-dismissible fade show" role="alert">  
  6.         @Text  
  7.         <button type="button" class="close" data-dismiss="alert" aria-label="Close" @onclick="OnDismiss">  
  8.             <span aria-hidden="true">×</span>  
  9.         </button>  
  10.     </div>  
  11. }  
  12.   
  13. @code {  
  14.     private bool showAlert = false;  
  15.   
  16.     [Parameter]  
  17.     public string Text { getset; } = string.Empty;  
  18.   
  19.     private async Task OnDismiss()  
  20.     {  
  21.         showAlert = false;  
  22.         await storage.SetAsync("NewsAlert.showAlert"false);  
  23.     }  
  24.     protected override async Task OnAfterRenderAsync(bool firstRender)  
  25.     {  
  26.         var result = await storage.GetAsync<bool>("NewsAlert.showAlert");   
  27.         bool oldValue = showAlert;  
  28.         showAlert = result.Success ? result.Value : true;        
  29.   
  30.         if (showAlert != oldValue)  
  31.         {  
  32.             StateHasChanged();  
  33.         }  
  34.     }  
  35. }  
You can now feel free to use this component like so,
  1. <NewsAlert Text="This is some news text" />   
Blazor Server - How to Store Encrypted Session Data in the Browser
 

Summary

 
Although it must be used responsibly, the client-side browser storage can prove very useful in your project! With Blazor Server and .NET 5, using this browser storage with your server-side components has never been easier.
 
Happy coding! Stay safe everyone.