“Strike! Blazor!” series introductory tutorial Chapter 1 6. Security

Time:2021-9-19

“Attack! Blazor!” is a video of blazor zero foundation introductory tutorial that I cooperated with Mr. Zhang Shanyou. This tutorial can enable a programmer who has never been in contact with blazor to master the ability to develop blazor applications.
Video address:https://space.bilibili.com/48…
Blazor webassembly is a single page application (SPA) framework, which is used to generate interactive client-side web applications using. Net, and uses c# instead of JavaScript to write front-end code
Due to the limited space in this series of articles, some codes are omitted, and the complete example code is as follows:https://github.com/TimChen44/…

Author: Chen Chaochao
Ant Design blazor project contributor, with more than ten years of working experience, has long been engaged in architecture and product development based on. Net technology stack, and now works in Chint Group.
Email: [email protected]
Readers are welcome to contact me with any questions and we will make progress together.

The basic functions of my todo application have been completed, but only you know your to-do, so we will add some security functions to our application this time.

Blazor authentication and authorization

Authentication

The security schemes of blazor server application and blazor webassembly application are different.

  • Blazor WebAssembly

The blazor webassembly application runs on the client. Since the user can bypass the client check because the user can modify all client code, authorization is only used to determine the UI options to display, as is the case for all client application technologies.

  • Blazor Server

The blazor server application runs through a real-time connection created using signalr. After the connection is established, the authentication of signalr based applications will be processed. Authentication can be based on cookies or some other holder token.

to grant authorization

AuthorizeViewThe component selectively displays UI content according to whether the user is authorized or not. This method is useful if you only need to display data for the user without using the user’s identity in the process logic.

<AuthorizeView>
  <Authorized>
    <!-- Verification passed display -- >
  </Authorized>
  <NotAuthorized>
    <!-- Verification failed display -- >
  </NotAuthorized>
</AuthorizeView>

Using token in blazor

In the blazor webassembly mode, because applications are running on the client, using token as identity authentication is a better choice.
The basic sequence diagram is as follows

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

However, token itself has the following two risks in terms of security:

  1. The token cannot be unregistered, so the server cannot do anything about illegal requests that can be sent within the validity period of the token.
  2. Tokens are stored on the client through AES encryption. In theory, they can be cracked offline. After cracking, tokens can be forged arbitrarily.

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

Transform todo

Then we transformed the previous todo project to support the login function.

ToDo.Shared

First, the dto required for front-end and back-end interaction is created

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 identity authentication code, etc

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 = true, // verify audience
                Validatelifetime = true, // verify the expiration time
                Validateissuersigningkey = true, // verify SecurityKey
                ValidAudience = "guetClient",//Audience
                Validussuer = "guetserver", // issuer, these two items are consistent with the setting of issuing JWT
                Issuersigningkey = new symmetricsecuritykey (encoding. Utf8. GetBytes ("1234567890123456789012345678901234567567789") // get the SecurityKey
            };
        });
}

The key and rules of token are defined here. These information can be put into the configuration during the actual project.

AuthController.cs

The administrative authentication controller is used to verify user identity, create token, etc.

[ApiController]
[Route("api/[controller]/[action]")]
public class AuthController : ControllerBase
{
    //Login
    [HttpPost]
    public UserDto Login(LoginDto dto)
    {
        //Get token through simulation
        var jwtToken = GetToken(dto.UserName);

        return new() { Name = dto.UserName, Token = jwtToken };
    }

    //Get the user, and call when the client page refreshes to get the user information
    [HttpGet]
    public UserDto GetUser()
    {
        If (user. Identity. Isauthenticated) // if the token is valid
        {
            var name = User.Claims.First(x => x.Type == ClaimTypes.Name).Value;// Take out the user ID from the token
            //Get token through simulation
            var jwtToken = GetToken(name);
            return new UserDto() { Name = name, Token = jwtToken };
        }
        else
        {
            return new UserDto() { Name = null, Token = null };
        }
    }

    public string GetToken(string name)
    {
        //Add account password verification code here

        var claims = new Claim[]
        {
            new Claim(ClaimTypes.Name,name),
            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 identity authentication

Add reference

  • Microsoft.AspNetCore.Components.Authorization

AuthenticationStateProvider

AuthenticationStateProvideryesAuthorizeViewComponents andCascadingAuthenticationStateComponent is the underlying service used to obtain authentication status.
Usually not used directlyAuthenticationStateProvider, the main disadvantage of direct use is that the component is not automatically notified if the underlying authentication status data changes. Secondly, there will always be some custom authentication logic in the project.
So we usually write a class to inherit it and rewrite 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()
    {
        //The user login status is obtained here
        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>
    ///Mark authorization
    /// </summary>
    /// <param name="loginModel"></param>
    /// <returns></returns>
    public void MarkUserAsAuthenticated(UserDto userDto)
    {
        HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);
        UserName = userDto.Name;

        //Here, the local policy should be configured according to the content returned by the server. As a demonstration, "admin" is added by default
        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 the token in the local storage to refresh the page without logging in
    }

    /// <summary>
    ///Mark logoff
    /// </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);
    }
}

NotifyAuthenticationStateChangedMethod notifies the user of authentication status data (such as authorizeview) to re render with the new data.
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);Add a token to the HTTP request header so that all subsequent requests will carry a token.

stayProgramMedium injectionAuthProviderServices for use elsewhere

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

stayProgramConfigure supported policies in

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

Login interface

add toLogin.razorComponent, code as follows

<div style="margin:100px">
  <Spin Spinning="isLoading">
    @if (model != null) {
    <form
      OnFinish="OnSave"
      Model="@model"
      LabelCol="new ColLayoutParam() {Span = 6 }"
    >
      < formitem label = "user name" >
        <input @bind-Value="context.UserName" />
      </FormItem>
      < formitem label = "password" >
        <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 succeeded");
            ((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result);
        }
        else
        {
            Msgsvr. Error ($"wrong user name or password");
        }
        isLoading = false;
       InvokeAsync( StateHasChanged);
    }
}

The login interface code is very simpleapi/Auth/LoginRequest, and judge whether the login is successful according to the returned result.
((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result);Tag authentication status has been modified.

Modify layout

modifyMainLayout.razorfile

<CascadingAuthenticationState>
  <AuthorizeView>
    <Authorized>
      <Layout>
        <Sider Style="overflow: auto;height: 100vh;position: fixed;left: 0;">
          < div class = "logo" > attack! Blazor!</ div>
          <menu Theme="MenuTheme.Dark" Mode="@MenuMode.Inline">
            < MenuItem routerlink = "/" > Home Page < / MenuItem >
            <menuitem RouterLink="/today" RouterMatch="NavLinkMatch.Prefix">
              One day for me
            </menuitem>
            <menuitem RouterLink="/star" RouterMatch="NavLinkMatch.Prefix">
              important mission
            </menuitem>
            <menuitem RouterLink="/search" RouterMatch="NavLinkMatch.Prefix">
              whole
            </menuitem>
          </menu>
        </Sider>
        <Layout Class="site-layout"> @Body </Layout>
      </Layout>
    </Authorized>
    <NotAuthorized>
      <ToDo.Client.Pages.Login></ToDo.Client.Pages.Login>
    </NotAuthorized>
  </AuthorizeView>
</CascadingAuthenticationState>

Displayed when authorization is passed<AuthorizeView>in<Authorized>Menu and home page, and vice versa<NotAuthorized>ofLoginComponent content.
When you need to display different contents according to permissions, you can use<AuthorizeView>ofPolicyProperty implementation, specifically inAuthenticationStateProviderBy configuring the policy, as in the exampleclaims.Add(new Claim("Admin", "Admin"));It was addedAdminPolicy, on the page, just<AuthorizeView Policy="Admin">You can control onlyAdminThe account of the policy displays its contents.
CascadingAuthenticationStateCascade identity state, which adopts the balzor component intermediate online system, so that we can use it in any level of componentsAuthorizeViewTo control the UI
AuthorizeViewThe component selectively displays UI content according to whether the user is authorized or not.
AuthorizedThe content in the component is displayed only when authorized.
NotAuthorizedThe content in the component can only be displayed without authorization.

modify_Imports.razorFiles, adding necessary references

@using Microsoft.AspNetCore.Components.Authorization

Run to see the effect

More about security

Security is a big topic. This chapter only introduces the simplest implementation method. For more information, it is recommended to read the official documents:
https://docs.microsoft.com/zh…

Second reply notice

We make a perfect statistics of the tasks in our todo application through several charts.

Learning materials

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