Blazer! Blazer! Blazor is a new application for developers who have never worked with Blazor. Blazor is a new application for developers.


Video Address:
https://space.bilibili.com/48…


Blazor WebAssembly is a single-page application (SPA) framework for generating interactive client-side Web applications using.NET, using C# instead of JavaScript to write front-end code


Due to the limited space in this series of articles, part of the code has been omitted. The complete example code is:
https://github.com/TimChen44/…

Author: Chen Chaochao


Ant Design Blazor project contributor, has more than 10 years of experience, long term based on. NET technology stack architecture and product development work, now working in Chint Group.


Email address: [email protected]


Readers are welcome to contact me if you have any questions. We will make progress together.

The basic functions of my Todo application have been completed, but only I know what I have ToDo, so we will add some security functions to our application this time.

Blazor authentication and authorization

The authentication

The security schemes for Blazor Server applications and Blazor WebAssembly applications are different.

  • Blazor WebAssembly

The Blazor WebAssembly application runs on the client. Because the user can bypass the client check, because the user can modify all the client code, authorization is used only to determine the UI options to display, as is the case with all client application technologies.

  • Blazor Server

The Blazor Server application runs through a live connection created using Signalr. Once the connection is established, authentication for Signalr-based applications is handled. Authentication can be based on cookies or some other holder token.

authorization

The AuthorizeView component selectively displays UI content based on whether the user is authorized or not. This approach is useful if you only want to display the data for the user and do not need to use the user’s identity in the process logic.

<AuthorizeView> <Authorized> <! > </Authorized> <NotAuthorized> <! > </NotAuthorized> </ authorizeView >

Use tokens in Blazor

In Blazor WebAssembly mode, since the application is running on the client, using Token as the method of authentication is a good choice.

The basic usage sequence diagram is shown below

For applications with low security requirements, this method is simple, easy to maintain, and there is no problem at all.

However, the Token itself has the following two security risks:

  1. The Token cannot be unsigned, so an illegal request can be sent within the Token’s validity period without the server’s ability to do anything about it.
  2. Tokens are stored on the client side with AES encryption, which can theoretically be cracked offline and then forged at will.

Therefore, when we encounter applications with very high security requirements, we need authentication service to verify the validity of tokens

Transform the ToDo

Then we reworked our previous Todo project to support login.

ToDo.Shared

The DTOs needed for the front-end interaction are created first

public class LoginDto { public string UserName { get; set; } public string Password { get; set; }}
public class UserDto { public string Name { get; set; } public string Token { get; set; }}

ToDo.Server

First transform the server, add necessary references, write the identity authentication code and so on

Add reference

  • Microsoft.AspNetCore.Authentication.JwtBearer

Startup.cs

Add JwtBearer configuration

public void ConfigureServices(IServiceCollection services) { //...... services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { Options. TokenValidationParameters = new TokenValidationParameters {ValidateIssuer = true, / / verify the Issuer ValidateAudience // ValidateIssuerSigningKey = True; // ValidateIssuerSigningKey = True; // ValidateIssuerSigningKey = True //Issuer = "GuetClient ",//Issuer =" GuetServer ",//Issuer: These two items are set to issue the JWT with issuerSigningKey = new SymmetricSecurityKey (Encoding UTF8. GetBytes (" 123456789012345678901234567890123456789 ")) / / get SecurityKey}; }); }

This defines the Token’s keys, rules, etc., which can be put into the configuration for the actual project.

AuthController.cs

Administrative authentication controller, used to authenticate users, create tokens, etc.

[ApiController] [Route("api/[controller]/[action]")] public class AuthController : ControllerBase {// [HttpPost] public UserDTO Login(LoginDto DTO) {// UserToken = getToken (DTO.username);  return new() { Name = dto.UserName, Token = jwtToken }; } // Get the user, When the page page refreshes the sort of client to obtain User information [HttpGet] public UserDto GetUser () {if (User. Identity. IsAuthenticated) / / Token, if effective {var name = User.Claims.First(x => x.Type == ClaimTypes.Name).Value; Var jwtToken = getToken (name); return new UserDto() { Name = name, Token = jwtToken }; } else { return new UserDto() { Name = null, Token = null }; }} public String GetToken(String Name) {var Claims = new Claims [] {new Claims (ClaimType.name,name), ClaimTypes (ClaimTypes.Name,name), ClaimTypes (ClaimTypes.Name,name), ClaimTypes (ClaimTypes. new Claim(ClaimTypes.Role,"Admin"), }; var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("123456789012345678901234567890123456789")); var expires = DateTime.Now.AddDays(30); var token = new JwtSecurityToken( issuer: "guetServer", audience: "guetClient", claims: claims, notBefore: DateTime.Now, expires: expires, signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)); return new JwtSecurityTokenHandler().WriteToken(token); }}

ToDo.Client

Transform the client to support authentication

Add reference

  • Microsoft.AspNetCore.Components.Authorization

AuthenticationStateProvider

AuthenticationStateProvider is AuthorizeView components and CascadingAuthenticationState components used in the base of the authentication service. Usually do not directly use AuthenticationStateProvider, directly using the main drawback is that if the basic authentication status data is changed, will not automatically inform components. The second is that there is always some custom authentication logic in the project. So we usually write a class that inherits it and rewrites some of our own logic.

//AuthProvider.cs public class AuthProvider : AuthenticationStateProvider { private readonly HttpClient HttpClient; public string UserName { get; set; } public AuthProvider(HttpClient httpClient) { HttpClient = httpClient; } public async override Task < AuthenticationState > GetAuthenticationStateAsync () {/ / get the user login here state var result = await HttpClient.GetFromJsonAsync<UserDto>($"api/Auth/GetUser"); if (result? .Name == null) { MarkUserAsLoggedOut(); return new AuthenticationState(new ClaimsPrincipal()); } else { var claims = new List<Claim>(); claims.Add(new Claim(ClaimTypes.Name, result.Name)); var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "apiauth")); return new AuthenticationState(authenticatedUser); }} / / / < summary > authorized / / / / / / tag < summary > / / / < param name = "loginModel" > < param > / / / < returns > < / returns > public void MarkUserAsAuthenticated(UserDto userDto) { HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token); UserName = userDto.Name; Var Claims = new List<Claim>(); var Claims = new List<Claim>(); claims.Add(new Claim(ClaimTypes.Name, userDto.Name)); claims.Add(new Claim("Admin", "Admin")); var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "apiauth")); var authState = Task.FromResult(new AuthenticationState(authenticatedUser)); NotifyAuthenticationStateChanged(authState); // Cihu can store tokens in local storage, // </ Summary > public void markUserAsLoggedOut () {// </ Summary > public void markUserAsLoggedOut () { HttpClient.DefaultRequestHeaders.Authorization = null; UserName = null; var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity()); var authState = Task.FromResult(new AuthenticationState(anonymousUser)); NotifyAuthenticationStateChanged(authState); }}

NotifyAuthenticationStateChanged method will inform authentication status data (such as AuthorizeView) users to use the new data. HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(“bearer”, userDto.Token); Add the Token to the HTTP request header so that all subsequent requests will carry the Token.

Inject the AuthProvider service into the Program for easy use elsewhere

//Program.cs
builder.Services.AddScoped<AuthenticationStateProvider, AuthProvider>();

Configure the supported policies in the Program

builder.Services.AddAuthorizationCore(option =>
{
    option.AddPolicy("Admin", policy => policy.RequireClaim("Admin"));
});

Login screen

Add the Login.Razor component as shown below

<div style="margin:100px"> <Spin Spinning="isLoading"> @if (model ! = null) {<form onFinish =" onSave "Model="@model" LabelCol=" New CollayoutParam () {Span = 6}" > <FormItem Label=" UserName ">) {<form onFinish =" onSave" Model="@model" LabelCol=" New CollayoutParam () {Span = 6}" > <FormItem Label=" UserName "> <input @bind -value ="context.UserName" /> </FormItem> <FormItem Label=" "> <input @bind -value ="context.Password" type="password" /> </FormItem> <FormItem WrapperColOffset="6"> <button type="@ButtonType.Primary" HtmlType = "submit" > login < / button > < / FormItem > < / form >} < / Spin > < / div >
public partial class Login { [Inject] public HttpClient Http { get; set; } [Inject] public MessageService MsgSvr { get; set; } [Inject] public AuthenticationStateProvider AuthProvider { get; set; } LoginDto model = new LoginDto(); bool isLoading; async void OnLogin() { isLoading = true; var httpResponse = await Http.PostAsJsonAsync<LoginDto>($"api/Auth/Login", model); UserDto result = await httpResponse.Content.ReadFromJsonAsync<UserDto>(); if (string.IsNullOrWhiteSpace(result? .token) == false) {msgsvr.success ($" login Success "); ((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result); } else {msgsvr. Error($" username or password Error "); } isLoading = false; InvokeAsync( StateHasChanged); }}

Login interface code is very simple, is to API /Auth/Login request, according to the result returned to judge whether the Login is successful. ((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result); Mark that the authentication status has been modified.

Modify the layout

Modify the MainLayout.Razor file

<CascadingAuthenticationState> <AuthorizeView> <Authorized> <Layout> <Sider Style="overflow: auto; height: 100vh; position: fixed; left: 0;" <div class="logo" class="logo" Blazor! </div> <menu Theme="MenuTheme.Dark" Mode="@MenuMode.Inline"> < MenuItem RouterLink="/"> Home </ MenuItem > < MenuItem RouterLink="/today" routerMatch =" navLinkMatch.prefix "> My day </menuitem> <menuitem routerLink ="/star" RouterMatch=" navLinkMatch.prefix "> important task </menuitem> <menuitem routerLink ="/search" routerMatch =" navLinkMatch.prefix "> all  </menuitem> </menu> </Sider> <Layout Class="site-layout"> @Body </Layout> </Layout> </Authorized> <NotAuthorized> <ToDo.Client.Pages.Login></ToDo.Client.Pages.Login> </NotAuthorized> </AuthorizeView> </CascadingAuthenticationState>

When the authorization is approved, the menu and home page of

in

will be displayed; otherwise, the Login component content of

will be displayed. When need according to the permission to display different content, you can use the < AuthorizeView > attributes of the Policy implementation, concrete is passed in AuthenticationStateProvider allocation strategy, Claims.Add(new Claim(“Admin”, “Admin”)); < authorizeView Policy=”Admin”> to control the display of Admin policy-only accounts on the page. CascadingAuthenticationState cascade status, it USES the Balzor component intermediate mechanism, In this way, we can use AuthorizeView in any level of the component to control the UI. The AuthorizeView component can selectively display the UI content according to whether the user is authorized or not. Content in the Authorized component is displayed only when it is Authorized. Content in the NotAuthorized component is only displayed if it is NotAuthorized.

Modify the _Imports. Razor file to add the necessary references

@using Microsoft.AspNetCore.Components.Authorization

Run to see the effect

More on Security

Security is a big topic, this chapter simply introduces the most simple way of implementation, there are more content recommended to read the official document: https://docs.microsoft.com/zh…

Time back to the trailer

We used a few charts to get a perfect tally of the tasks in our TODO application.

Learning materials

Learn more about Blazor:https://aka.ms/LearnBlazor