Laden...

Blazor Server (.NET 8) CustomAuthStateProvider

Letzter Beitrag vor 9 Tagen 2 Posts 99 Views
Blazor Server (.NET 8) CustomAuthStateProvider

Moin Moin,

ich habe einen CustomAuthStateProvider und einen LocalStorageService. Ich verwende zudem Electron.NET, falls das wichtig zu wissen ist.

App.razor:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <base href="/" />  
   <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
   <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />  
   <link rel="icon" type="image/ico" href="favicon.ico" />
   <HeadOutlet @rendermode="InteractiveServer" />
</head>
<body>
   <Routes @rendermode="InteractiveServer" />
   <script>process = undefined;</script> <!-- https://github.com/ElectronNET/Electron.NET/issues/823 -->
   <script src="_framework/blazor.web.js"></script>
   <script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>
</html>

Routes.razor:

<Router AppAssembly="@typeof(Program).Assembly">
   <Found Context="routeData">
       <AuthorizeRouteView RouteData="@routeData" DefaultLayout="typeof(Layout.MainLayout)">
           <NotAuthorized>
               <Redirect To="/login" />
           </NotAuthorized>
       </AuthorizeRouteView>
       <FocusOnNavigate RouteData="routeData" Selector="h1" />
   </Found>
   <NotFound>
       <LayoutView Layout="typeof(Layout.MainLayout)">
           <p>Sorry, there's nothing at this address.</p>
       </LayoutView>
   </NotFound>
</Router>

CustomAuthStateProvider:

public class CustomAuthStateProvider : AuthenticationStateProvider
{
   private readonly LocalStorageService _localStorageService;
   public CustomAuthStateProvider(LocalStorageService localStorageService, HttpClient http)
   {
       _localStorageService = localStorageService;
   }
   public override async Task<AuthenticationState> GetAuthenticationStateAsync()
   {
       string accessToken = await _localStorageService.GetValueAsyncOrDefault<string>("accessToken");
       var identity = new ClaimsIdentity();
       if (!string.IsNullOrEmpty(accessToken))
       {
           identity = new ClaimsIdentity(ParseClaimsFromJwt(accessToken), "jwt");
           var claims = ParseClaimsFromJwt(accessToken);
           foreach (var claim in claims)
           {
               if (claim.Type == ClaimTypes.Role)
               {
                   try
                   {
                       var rolesArray = JsonSerializer.Deserialize<List<string>>(claim.Value);
                       foreach (var role in rolesArray)
                       {
                           identity.AddClaim(new Claim(ClaimTypes.Role, role));
                       }
                   }
                   catch (JsonException)
                   {
                   }
               }
           }
       }
       var user = new ClaimsPrincipal(identity);
       var state = new AuthenticationState(user);
       NotifyAuthenticationStateChanged(Task.FromResult(state));
       return state;
   }
   public static IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
   {
       var payload = jwt.Split('.')[1];
       var jsonBytes = ParseBase64WithoutPadding(payload);
       var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
       return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
   }
   private static byte[] ParseBase64WithoutPadding(string base64)
   {
       switch (base64.Length % 4)
       {
           case 2: base64 += "=="; break;
           case 3: base64 += "="; break;
       }
       return Convert.FromBase64String(base64);
   }
}

LocalStorageService:

public class LocalStorageService
{
   private readonly ILocalStorageService _localStorage;
   public LocalStorageService(ILocalStorageService localStorage)
   {
       _localStorage = localStorage;
   }
   public async Task SetValueAsync<T>(string key, T value, TimeSpan? expiration = null)
   {
       var item = new LocalStorageItem<T>
       {
           Value = value,
           Expiration = expiration.HasValue ? DateTime.UtcNow.Add(expiration.Value) : DateTime.MaxValue
       };
       await _localStorage.SetItemAsync(key, item);
   }
   public async Task<T> GetValueAsync<T>(string key)
   {
       var item = await _localStorage.GetItemAsync<LocalStorageItem<T>>(key);
       if (item == null || item.IsExpired)
       {
           await RemoveValueAsync(key);
           throw new LocalStorageItemExpiredException();
       }
       return item.Value;
   }
   public async Task<T> GetValueAsyncOrDefault<T>(string key)
   {
       var item = await _localStorage.GetItemAsync<LocalStorageItem<T>>(key);
       if (item == null || item.IsExpired)
       {
           await RemoveValueAsync(key);
           return default;
       }
       return item.Value;
   }
   public async Task<bool> HasKeyAsync(string key)
   {
       var item = await _localStorage.GetItemAsync<LocalStorageItem<object>>(key);
       return item != null && !item.IsExpired;
   }
   public async Task UpdateValueAsync<T>(string key, T value, TimeSpan? expiration = null)
   {
       await SetValueAsync(key, value, expiration);
   }
   public async Task RemoveValueAsync(string key)
   {
       await _localStorage.RemoveItemAsync(key);
   }
   public async Task ClearAsync()
   {
       await _localStorage.ClearAsync();
   }
}

Das Problem ist konkret folgendes:
JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.

Sprich, ich kann den LocalStorage in der Prerendering-Phase nicht verwenden. Ich habe dazu bisher nur blöde Workarounds gefunden. Gibt es da nicht eine recht einfache Möglichkeit?

Oder habe ich einfach nur was falsch konfiguriert? Ich habe den Auth-State-Provider natürlich richtig registriert.

Danke im Voraus für jede Hilfe!

Wenn man etwas nicht einfach erklären kann, hat man es nicht verstanden.

-Albert Einstein

Gelöst durch das Ausschalten vom Prerendering.

Ich habe das Prerendering einfach in der App.razor deaktiviert. Zwar keine sehr schöne, aber funktionale Lösung.

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <base href="/" />  
   <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
   <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />  
   <link rel="icon" type="image/ico" href="favicon.ico" />
   <HeadOutlet @rendermode="@(new InteractiveServerRenderMode(prerender: false))" />
</head>
<body>
   <Routes @rendermode="@(new InteractiveServerRenderMode(prerender: false))" />
   <script>process = undefined;</script> <!-- https://github.com/ElectronNET/Electron.NET/issues/823 -->
   <script src="_framework/blazor.web.js"></script>
   <script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>
</html>

Wenn man etwas nicht einfach erklären kann, hat man es nicht verstanden.

-Albert Einstein