Reference:
https://learn.microsoft.com/en-us/aspnet/core/security/?view=aspnetcore-9.0
https://blog.csdn.net/ousetuhou/article/details/135392012

  • Authentication

Authentication is a process in which a user provides credentials that are then compared to those stored in an operating system, database, app or resource. If they match, users authenticate successfully, and can then perform actions that they're authorized for, during an authorization process. The authorization refers to the process that determines what a user is allowed to do.

An authentication scheme's authenticate action is responsible for constructing the user's identity based on request context.
An authentication challenge is invoked by Authorization when an unauthenticated user requests an endpoint that requires authentication. A challenge action should let the user know what authentication mechanism to use to access the requested resource.
An authentication scheme's forbid action is called by Authorization when an authenticated user attempts to access a resource they're not permitted to access.

How to use Identity?
1.Add a class named MyUser(any name you want) class that inherits from IdentityUser, T is type of primary key.

public class MyUser : IdentityUser
{
    public string? WechatAccout { get; set; }
}

2.Add a class named MyRole(any name you want) class that inherits from IdentityRole, T is type of Primary key.
3.Add a class named ApplicationDbContext(any name you want) class that inherits from IdentityDbContext, and override the constructor which has a options parameter, makes it possible to configure the database for different environments.
When using Identity with support for roles, an IdentityDbContext class should be used.
It's also possible to use Identity without roles(only claims), in which case an IdentityUserContext class should be used.

public class ApplicationDbContext : IdentityDbContext
{
    public ApplicationDbContext(DbContextOptions options) : base(options)
    {
    }
}

4.Add configuration to DI framework.

// configure Identity to use a database
builder.Services.AddDbContext(options
     =>
{
    var connectionString = builder.Configuration.GetValue("MySQLConnectionString", string.Empty);
    options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
});

builder.Services.AddDataProtection();
builder.Services.AddIdentityCore(
    options =>
    {
        // for test, reduce the strength of password
        options.Password.RequireDigit = false;
        options.Password.RequireLowercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequireUppercase = false;
        options.Password.RequiredLength = 6;
        options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
        options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
    });
var identityBuilder = new IdentityBuilder(typeof(MyUser), typeof(MyRole), builder.Services);
identityBuilder.AddEntityFrameworkStores()
    .AddDefaultTokenProviders().AddRoleManager>()
    .AddUserManager>();

5.Add a DbContextFactory implement IDesignTimeDbContextFactory, because EF Core may not be able to migrate.

public class DbContextDesignTimeFactory: IDesignTimeDbContextFactory
{
    public ApplicationDbContext CreateDbContext(string[] args)
    {
        DbContextOptionsBuilder builder = new DbContextOptionsBuilder();
        var connectionString = arg[0];
        builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
        return new ApplicationDbContext(builder.Options);
    }
}

6.Database migrate

How to use JWT?
1.Configurate JWT

"JwtTokenOption": {
    "Issuer": "ChenLi",
    "Audience": "EveryOne",
    "IssuerSigningKey": "ChenLi$%%^&%*!&^@*GUH976&^^&T*u34",
    "AccessTokenExpiresMinutes": "30"
  }

2.Create a option

public class JwtTokenOption
{
    public const string JwtKey = "JwtTokenOption";
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public string IssuerSigningKey { get; set; }
    public int AccessTokenExpiresMinutes { get; set; }
}

3.Add JwtTokenOption Configuration

builder.Services.Configure(builder.Configuration.GetSection(JwtTokenOption.JwtKey));

4.Install JWTBearer NuGet package

Microsoft.AspNetCore.Authentication.JwtBearer

5.Add JwtBearer to Authentication

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    var jwtTokenOption = builder.Configuration.GetSection(JwtTokenOption.JwtKey).Get();
    options.RequireHttpsMetadata = false;
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuer = true,
        ValidIssuer = jwtTokenOption.Issuer,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtTokenOption.IssuerSigningKey)),
        ValidateAudience = true,
        ValidAudience = jwtTokenOption.Audience,
        ValidateLifetime = true,
        ClockSkew = TimeSpan.FromMinutes(1)
    };
    options.Events = new JwtBearerEvents()
    {
        OnAuthenticationFailed = context =>
        {
            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
            {
                context.Response.Headers.Add("Token-Expired", "true");
            }

            return Task.CompletedTask;
        }
    };
});

6.Add Class to create Token

public class TokenHelper(IOptionsSnapshot jwtTokenOption) : ITokenHelper
{
    public JwtToken CreateToken(T entity) where T : class
    {
        List claims = new List();
        foreach (var item in entity.GetType().GetProperties())
        {
            object obj = item.GetValue(entity);
            string value = "";
            if (obj != null)
            {
                value = obj.ToString();
            }
            claims.Add(new Claim(item.Name, value));
        }

        return CreateTokenString(claims);
    }

    public JwtToken CreateToken(Dictionary KeyValuePairs)
    {
        List claims = new List();
        foreach (var item in KeyValuePairs)
        {
            claims.Add(new Claim(item.Key, item.Value));
        }

        return CreateTokenString(claims);
    }

    private JwtToken CreateTokenString(IEnumerable claims)
    {
        DateTime expires = DateTime.Now.AddHours(jwtTokenOption.Value.AccessTokenExpiresMinutes);
        var token = new JwtSecurityToken(
            issuer: jwtTokenOption.Value.Issuer,
            audience: jwtTokenOption.Value.Audience,
            claims: claims,
            notBefore: DateTime.Now,
            expires: expires,
            signingCredentials: new SigningCredentials(
                new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtTokenOption.Value.IssuerSigningKey)),
                SecurityAlgorithms.HmacSha256
                )
            );
        return new JwtToken()
        {
            TokenStr = new JwtSecurityTokenHandler().WriteToken(token),
            Expires = expires
        };
    }
}

7.return token to client

[HttpPost]
    public async Task Login(String username, String password)
    {
        ResponseModel response = new ResponseModel();
        var user = await userManager.FindByNameAsync(username);
        if (user == null)
        {
            response.Code = 401;
            response.Message = "Invalid username or password";
            return Unauthorized(response);
        }

        var checkPassword = await userManager.CheckPasswordAsync(user, password);
        Dictionary dir = new Dictionary()
        {
            { "username", user.UserName },
        };
        response.Code = 200;
        response.Message = "Success";
        response.TokenInfo = tokenHelper.CreateToken(dir);
        return Ok(response);
    }

8.Add [Authorize] to action or controller, Header must have jwt or will be 401

Headers:
Authorization Bearer
[HttpPost]
    [Authorize]
    public async Task SendRestPasswordToken(string username)
    {
}