Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4839

.NET 10 JWT authentication - Issue with AuthenticationState Not Updating in AuthorizeView

$
0
0

I'm working on implementing JWT authentication in a Blazor Server application with .NET 10. The issue I'm facing is that while I'm able to extract the JWT token, decode it, and see the claims (like role and personalId), the AuthorizeView component is not behaving as expected.

Specifically, it's always rendering the NotAuthorized block even when the user is authenticated, even though I can see the claims in the token.

Here is my CustomAuthenticationService:

public class CustomAuthenticationService : AuthenticationStateProvider{    private readonly ILocalStorageService _localStorage;    private readonly HttpClient _httpClient;    private readonly NavigationManager _navigationManager;    private string _cachedToken;    private AuthenticationState _currentState; // Hold the current authentication state    public CustomAuthenticationService(ILocalStorageService localStorage, HttpClient httpClient, NavigationManager navigationManager)    {        _localStorage = localStorage;        _httpClient = httpClient;        _navigationManager = navigationManager;        _currentState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));    }    // Overriding GetAuthenticationStateAsync to return the current authentication state    public override async Task<AuthenticationState> GetAuthenticationStateAsync()    {        if (_cachedToken != null)        {            // Return the cached token if it's already loaded            return BuildAuthenticationStateFromToken(_cachedToken);        }        // Return a default, unauthenticated state while loading the token        return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));    }    // This method loads the token from localStorage and updates the authentication state    public async Task LoadTokenFromLocalStorageAsync()    {        var token = await _localStorage.GetItemAsync<string>("authToken");        if (!string.IsNullOrEmpty(token))        {            _cachedToken = token;            // Notify that the authentication state has changed            NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());        }        else        {            _cachedToken = null;        }        //var token = await _localStorage.GetItemAsync<string>("authToken");        //ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity());        //if (!string.IsNullOrEmpty(token))        //{        //    // Decode the JWT or extract claims (you can extend this as needed)        //    var claims = GetClaimsFromToken(token);        //    user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));        //}        //// Update the authentication state        //_currentState = new AuthenticationState(user);        //// Notify any subscribers that the authentication state has changed        //NotifyAuthenticationStateChanged(Task.FromResult(_currentState));    }    // This method will be used to log out the user    public async Task Logout()    {        _cachedToken = null; // Clear the cached token        await _localStorage.RemoveItemAsync("authToken"); // Remove token from localStorage        _httpClient.DefaultRequestHeaders.Authorization = null; // Remove the authorization header        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); // Notify state change        _navigationManager.NavigateTo("/login", true); // Navigate to the login page    }    // This method builds the AuthenticationState from the JWT token    private AuthenticationState BuildAuthenticationStateFromToken(string token)    {        // Parse claims from the JWT token        var claims = ParseClaimsFromJwt(token);        // Create identity using the claims        var identity = new ClaimsIdentity(claims, "jwt");        // Create a ClaimsPrincipal from the identity        var user = new ClaimsPrincipal(identity);        // Return the authentication state with the ClaimsPrincipal        return new AuthenticationState(user);    }    // Helper method to parse the JWT and extract claims    private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)    {        var payload = jwt.Split('.')[1]; // JWT payload is the second part of the token        var jsonBytes = WebEncoders.Base64UrlDecode(payload); // Decode the payload from base64 URL        var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes); // Deserialize the JSON payload        return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())); // Return claims    }    // This method will be triggered after the component is rendered    public async Task OnAfterRenderAsync(bool firstRender)    {        if (firstRender)        {            // Only load the token after the component is rendered on the client side            await LoadTokenFromLocalStorageAsync();        }    }    // Helper method to extract claims from JWT (decoding the token payload)    public IEnumerable<Claim> GetClaimsFromToken(string token)    {        // Split the JWT token into parts (header, payload, signature)        var parts = token.Split('.');        if (parts.Length != 3)        {            return Enumerable.Empty<Claim>();        }        // Decode the payload part of the JWT        var payload = parts[1];        var jsonBytes = WebEncoders.Base64UrlDecode(payload);        var json = Encoding.UTF8.GetString(jsonBytes);        // Deserialize the JSON payload to get the claims        var claimsDict = JsonSerializer.Deserialize<Dictionary<string, object>>(json);        var claims = claimsDict?.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())) ?? Enumerable.Empty<Claim>();        return claims;    }    public async Task<string> GetProtectedDataAsync()    {        var token = await _localStorage.GetItemAsync<string>("authToken");        if (string.IsNullOrEmpty(token))        {            throw new Exception("User is not authenticated");        }        _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);        var response = await _httpClient.GetStringAsync("api/protected-data");        return response;    }}

Here it is part of MainLayout.razor:

@using System.Security.Claims@inherits LayoutComponentBase@rendermode InteractiveServer@inject IStringLocalizer<SharedResource> L@inject CustomAuthenticationService _authService;@inject NavigationManager _navigationManager</AuthorizeView><Authorized>        .....</Authorized><NotAuthorized>        @if (_navigationManager.Uri.Contains("/Auth/Login"))        {<p>Please login to continue.</p><Login/>        }        else        {<p>You need to login first.</p><a href="/Auth/Login">Login HERE</a><p>@(string.IsNullOrEmpty(role) ? string.Empty : role) </p><p>@(string.IsNullOrEmpty(personalId) ? string.Empty : personalId) </p>        }</NotAuthorized></AuthorizeView>@code {    private bool isAuthenticated;    private string? role;    private string? personalId;    private ClaimsPrincipal _user;    // Called whenever the component parameters are set or changed.    protected override async Task OnParametersSetAsync()    {        await UpdateAuthenticationState();        StateHasChanged(); // Trigger re-rendering    }    private bool hasRendered = false;    protected override async Task OnAfterRenderAsync(bool firstRender)    {        if (firstRender || hasRendered == false)        {            await UpdateAuthenticationState();            hasRendered = true;        }    }    protected override async Task OnInitializedAsync()    {        await UpdateAuthenticationState();        StateHasChanged(); // Trigger re-rendering    }    private async Task UpdateAuthenticationState()    {        AuthenticationState state = await _authService.GetAuthenticationStateAsync();        _user = state.User;        isAuthenticated = _user.Identity.IsAuthenticated;        if (isAuthenticated)        {            // Extract claims (role and PersonalId)            role =_user.FindFirst("http://schemas.microsoft.com/ws/2008/06/identity/claims/role")?.Value;            personalId = _user.FindFirst("PersonalId")?.Value;        }        else        {            role = null;            personalId = null;        }        StateHasChanged(); // Trigger re-rendering    }    private void NavigateToLogin()    {        _navigationManager.NavigateTo("/Auth/Login", true);    }}

Any help it will be much appreciated, and thank you in advance!


Viewing all articles
Browse latest Browse all 4839

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>