最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Blazor WASM JWT - How to get auth state in MainLayout - Stack Overflow

programmeradmin5浏览0评论

I wanted to set up authentication in Blazor WebAssembly, JWT, Net 8, but I encountered a problem. When I place tags in an interactive component, like here:

@page "/"
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
@inject IStringLocalizer<Login> Localizer
@using Microsoft.AspNetCore.Components.Authorization

<CascadingAuthenticationState>
    <AuthorizeView>
        <Authorized>
            Authorized
        </Authorized>
        <NotAuthorized>
            NotAuthorized
        </NotAuthorized>
    </AuthorizeView>
</CascadingAuthenticationState>

My AuthenticationStateProvider works fine and returns Authorized or NotAuthorized. But when I try to do it in MainLayout.razor, like here:

@using Microsoft.AspNetCore.Components.Authorization
@inherits LayoutComponentBase

<CascadingAuthenticationState>
    <AuthorizeView>
        <Authorized>
            Authorized
        </Authorized>
        <NotAuthorized>
            NotAuthorized
        </NotAuthorized>
    </AuthorizeView>
</CascadingAuthenticationState>

I always get NotAuthorized. This happens because the application is not rendered interactively in MainLayout.razor. How can I fix this?

My JwtAuthenticationStateProvider.cs:

using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.JSInterop;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace WMS.Client.Providers;

public class JwtAuthenticationStateProvider(ILocalStorageService LocalStorage, IJSRuntime JSRuntime) : AuthenticationStateProvider
{
    private readonly ILocalStorageService _localStorage = LocalStorage;
    private readonly IJSRuntime _JSRuntime = JSRuntime;
    private const string TokenKey = "authToken";

    public sealed override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        if (_JSRuntime is not IJSInProcessRuntime)
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        
        var token = await _localStorage.GetItemAsync<string>(TokenKey);

        if (string.IsNullOrEmpty(token))
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));

        var user = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"));
        return new AuthenticationState(user);
    }

    public async Task Login(string token)
    {
        await _localStorage.SetItemAsync(TokenKey, token);
        
        var user = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"));
        NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
    }

    public async Task Logout()
    {
        await _localStorage.RemoveItemAsync(TokenKey);
        NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))));
    }

    private static IEnumerable<Claim> ParseClaimsFromJwt(string token)
    {
        var handler = new JwtSecurityTokenHandler();
        var jwt = handler.ReadJwtToken(token);

        return jwt.Claims;
    }
}

My Routes.razor:

@using Microsoft.AspNetCore.Components.Authorization
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(Layout.MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

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="app.css" rel="stylesheet" />
    <link href="WMS.styles.css" rel="stylesheet" />
    <link href="+Icons" rel="stylesheet">
    <HeadOutlet />
    <script src="js/site.js"></script>
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>

Program.cs in client side:

using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using System.Globalization;
using WMS.Client.Providers;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.Services.AddBlazoredLocalStorage();
builder.Services.AddLocalization(options => { options.ResourcesPath = "Resources"; });
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<JwtAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider, JwtAuthenticationStateProvider>();

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");

await builder.Build().RunAsync();

Program.cs in server side:

using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using WMS.Client.Providers;
using WMS.Components;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveWebAssemblyComponents();

builder.Services.AddBlazoredLocalStorage();
builder.Services.AddAuthorization();

builder.Services.AddScoped<JwtAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider, JwtAuthenticationStateProvider>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
}

app.UseStaticFiles();
app.UseAntifery();

app.MapRazorComponents<App>()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(WMS.Client._Imports).Assembly);

app.Run();

I wanted to set up authentication in Blazor WebAssembly, JWT, Net 8, but I encountered a problem. When I place tags in an interactive component, like here:

@page "/"
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
@inject IStringLocalizer<Login> Localizer
@using Microsoft.AspNetCore.Components.Authorization

<CascadingAuthenticationState>
    <AuthorizeView>
        <Authorized>
            Authorized
        </Authorized>
        <NotAuthorized>
            NotAuthorized
        </NotAuthorized>
    </AuthorizeView>
</CascadingAuthenticationState>

My AuthenticationStateProvider works fine and returns Authorized or NotAuthorized. But when I try to do it in MainLayout.razor, like here:

@using Microsoft.AspNetCore.Components.Authorization
@inherits LayoutComponentBase

<CascadingAuthenticationState>
    <AuthorizeView>
        <Authorized>
            Authorized
        </Authorized>
        <NotAuthorized>
            NotAuthorized
        </NotAuthorized>
    </AuthorizeView>
</CascadingAuthenticationState>

I always get NotAuthorized. This happens because the application is not rendered interactively in MainLayout.razor. How can I fix this?

My JwtAuthenticationStateProvider.cs:

using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.JSInterop;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace WMS.Client.Providers;

public class JwtAuthenticationStateProvider(ILocalStorageService LocalStorage, IJSRuntime JSRuntime) : AuthenticationStateProvider
{
    private readonly ILocalStorageService _localStorage = LocalStorage;
    private readonly IJSRuntime _JSRuntime = JSRuntime;
    private const string TokenKey = "authToken";

    public sealed override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        if (_JSRuntime is not IJSInProcessRuntime)
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        
        var token = await _localStorage.GetItemAsync<string>(TokenKey);

        if (string.IsNullOrEmpty(token))
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));

        var user = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"));
        return new AuthenticationState(user);
    }

    public async Task Login(string token)
    {
        await _localStorage.SetItemAsync(TokenKey, token);
        
        var user = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"));
        NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
    }

    public async Task Logout()
    {
        await _localStorage.RemoveItemAsync(TokenKey);
        NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))));
    }

    private static IEnumerable<Claim> ParseClaimsFromJwt(string token)
    {
        var handler = new JwtSecurityTokenHandler();
        var jwt = handler.ReadJwtToken(token);

        return jwt.Claims;
    }
}

My Routes.razor:

@using Microsoft.AspNetCore.Components.Authorization
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(Layout.MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

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="app.css" rel="stylesheet" />
    <link href="WMS.styles.css" rel="stylesheet" />
    <link href="https://fonts.googleapis/icon?family=Material+Icons" rel="stylesheet">
    <HeadOutlet />
    <script src="js/site.js"></script>
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
</body>

</html>

Program.cs in client side:

using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using System.Globalization;
using WMS.Client.Providers;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.Services.AddBlazoredLocalStorage();
builder.Services.AddLocalization(options => { options.ResourcesPath = "Resources"; });
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<JwtAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider, JwtAuthenticationStateProvider>();

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");

await builder.Build().RunAsync();

Program.cs in server side:

using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using WMS.Client.Providers;
using WMS.Components;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveWebAssemblyComponents();

builder.Services.AddBlazoredLocalStorage();
builder.Services.AddAuthorization();

builder.Services.AddScoped<JwtAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider, JwtAuthenticationStateProvider>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
}

app.UseStaticFiles();
app.UseAntifery();

app.MapRazorComponents<App>()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(WMS.Client._Imports).Assembly);

app.Run();
Share Improve this question edited Mar 25 at 5:51 Qiang Fu 9,3871 gold badge6 silver badges16 bronze badges asked Mar 20 at 23:33 Mikołaj SzczepekMikołaj Szczepek 132 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

You could set the page rendermode (include mainlayout) dynamically based on the route in App.razor

    <HeadOutlet @rendermode="PageRenderMode()" />
...
    <Routes @rendermode="PageRenderMode()" />
...

@code {
    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

    private IComponentRenderMode? PageRenderMode()
    {
        //if route start with acount use your rendermode
        if (HttpContext.Request.Path.StartsWithSegments("/Account"))
        {
            return new InteractiveWebAssemblyRenderMode(prerender: false);
        }
        return null;
    }
}
发布评论

评论列表(0)

  1. 暂无评论