简体   繁体   中英

.NET Core 3.1 Web API and Blazor Server Side JWT Authentication

I would like to understand, how it is possible to set up JWT authentication for Blazor Server Side Apps ?

Let me draw up an example: Let's say we have a .NET Core 3.1 Web API project. The project has its own TokenController implementation, which gives out JWT for a valid user / password combination. All other Controllers require such a token for each request.

The Middleware for validating the Authentication is configured like so:

// enabling JWT bearer scheme
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => 
    {
        options.TokenValidationParameters = new TokenValidationParameters 
        {
            // TOKEN VALIDATION PARAMETERS
        };
    });

// applying default authentication policy
services.AddMvc(o => 
{
    o.EnableEndpointRouting = false;
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
    o.Filters.Add(new AuthorizeFilter(policy));
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

Up until here, this is working perfectly fine.

Now I would like to add a nice Blazor Server Side UI to this project, and I just can't wrap my head around how to do the Authentication then?

According to Microsoft Docs, the Authentication for Server Side Apps is supposed to take place at establishing the SignalR connection:

Blazor Server authentication

Blazor Server apps operate over a real-time connection that's created using SignalR. Authentication in SignalR-based apps is handled when the connection is established. Authentication can be based on a cookie or some other bearer token.

(source: https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1&tabs=visual-studio )

Unfortunately I am not able to figure out how this works - the tutorials and tips I found are either for Client Side Blazor or use Cookie / Identity...

Any ideas?

I set up Blazor Server with authentication, and then added JWT auth for API. So I think it's the same thing in reverse.

Here's what I did:

In Startup.cs

ConfigureServices method (I think the order might matter, not sure):

services.AddDefaultIdentity<IdentityUser>()
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

// not sure if this line is required for Blazor auth
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();

services.AddAuthentication()
    .AddCookie(cfg => cfg.SlidingExpiration = true)
    .AddJwtBearer(x =>
    {
        // options
    });

For Blazor pages

// SamplePage.razor.cs
[Route("page-path")]
[Authorize]
public partial class SamplePage
{ }

// or like this in SamplePage.razor
@page "page-path"
@attribute [Authorize]

For API controller

[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class SampleController : ControllerBase
{ }

I found the content in this page very helpful in getting JWT to work after I had started with a Blazor Server-side project. https://jasonwatmore.com/post/2019/10/11/aspnet-core-3-jwt-authentication-tutorial-with-example-api

did you solve your solution? For Blazor server the best option would be to use ProtectedLocalStorage and some sort of AuthenticationStateProvider . Look at my repo. I hope it will help https://github.com/DudnykOleksandr/template-blazor-jwt-authentication

public class BlazorAppLoginService
{
    private readonly string TokenKey = nameof(TokenKey);

    private readonly ProtectedLocalStorage localStorage;
    private readonly NavigationManager navigation;
    private readonly IUsersService usersService;
    private readonly IConfiguration configuration;

    public BlazorAppLoginService(ProtectedLocalStorage localStorage, NavigationManager navigation, IUsersService usersService, IConfiguration configuration)
    {
        this.localStorage = localStorage;
        this.navigation = navigation;
        this.usersService = usersService;
        this.configuration = configuration;
    }

    public async Task<bool> LoginAsync(string userName, string password)
    {
        var isSuccess = false;

        var token = await usersService.LoginAsync(userName, password);
        if (!string.IsNullOrEmpty(token))
        {
            isSuccess = true;
            await localStorage.SetAsync(TokenKey, token);
        }

        return isSuccess;
    }


    public async Task<List<Claim>> GetLoginInfoAsync()
    {
        var emptyResut = new List<Claim>();
        ProtectedBrowserStorageResult<string> token = default;
        try
        {
            token = await localStorage.GetAsync<string>(TokenKey);
        }
        catch (CryptographicException)
        {
            await LogoutAsync();
            return emptyResut;
        }

        if (token.Success && token.Value != default)
        {
            var claims = JwtTokenHelper.ValidateDecodeToken(token.Value, configuration);
            if (!claims.Any())
            {
                await LogoutAsync();
            }
            return claims;
        }
        return emptyResut;
    }

    public async Task LogoutAsync()
    {
        await RemoveAuthDataFromStorageAsync();
        navigation.NavigateTo("/", true);
    }


    private async Task RemoveAuthDataFromStorageAsync()
    {
        await localStorage.DeleteAsync(TokenKey);
    }
public class CustomBlazorAuthStateProvider : AuthenticationStateProvider
{
    private readonly BlazorAppLoginService blazorAppLoginService;

    public CustomBlazorAuthStateProvider(BlazorAppLoginService blazorAppLoginService)
    {
        this.blazorAppLoginService = blazorAppLoginService;
    }

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var claims = await blazorAppLoginService.GetLoginInfoAsync();
        ClaimsIdentity claimsIdentity;
        if (claims.Any())
        {
            claimsIdentity = new ClaimsIdentity(claims, "Bearer");
        }
        else
        {
            claimsIdentity = new ClaimsIdentity();
        }
        var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
        return new AuthenticationState(claimsPrincipal);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM