Identity framework and use of JWT

1.Authentication verifies the user identity of the visitor. (Whether the user logged in successfully)

Whether the Authorization user identity has access rights to resources. (Whether the user has permission to access this address)

(1) Install the nuget package: ASP.NET Core Identity Entity Framework Core.

(2) Create three types:

public class MyUser:IdentityUser<long>//long is the primary key user table
{
}
public class MyRole: IdentityRole<long>//Role table
{
}
public class MyDbContext: IdentityDbContext<MyUser,MyRole,long>//Remember the three generics here
{
    public MyDbContext(DbContextOptions<MyDbContext> options)
    :base(options)
    {
    }
}

(3) Set the Identity framework in the program:

//Configure connection string service
builder.Services.AddDbContext<MyDbContext>(opt =>
{
    string? sqlsr = builder.Configuration.GetSection("DBcontext").Value;
    opt.UseSqlServer(sqlsr);
});
//Encryption service
builder.Services.AddDataProtection();
//Configure user services
builder.Services.AddIdentityCore<MyUser>(options => {
    options.Lockout.MaxFailedAccessAttempts = 5;//The account will be locked after five errors
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(0.005);//Set the account lock time
    options.Password.RequireDigit = false;//Whether there must be a number
    options.Password.RequireLowercase = false;//Whether there must be lowercase letters
    options.Password.RequireNonAlphanumeric = false;//Whether non-alphanumeric is required
    options.Password.RequireUppercase=false;//Whether uppercase letters are required
    options.Password.RequiredLength = 6;//Require password length
    options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;//Password recognition
    options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;//Email confirmation
});
//The combination of identity service framework
IdentityBuilder idBuilder=new IdentityBuilder(typeof(MyUser), typeof(MyRole),builder.Services);
idBuilder.AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders()
    .AddUserManager<UserManager<MyUser>>().AddRoleManager<RoleManager<MyRole>>();

(4) It may not be recognized during database migration, so build a class that inherits the IDesignTimeDbContextFactory interface.

public class DbContextDesignTimeFactory : IDesignTimeDbContextFactory<MyDbContext>
    {
        public MyDbContext CreateDbContext(string[] args)
        {
            DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
            //Configure the connection string in the environment variable and obtain the connection string from the environment variable
            string? constr = Environment.GetEnvironmentVariable("haha").ToString();
            builder.UseSqlServer(constr);
            return new MyDbContext(builder.Options);
        }
    }

(5) User login verification (if you need to use parameters, you can change it yourself)

[HttpPost]
        public async Task<ActionResult<string>> Test1()
        {
            if (await roleMger.RoleExistsAsync("admin") == false)
            {
                MyRole myRole = new MyRole { Name = "admin" };
                var result = await roleMger.CreateAsync(myRole);
                if (!result.Succeeded)
                {
                    return BadRequest("roleMger CreateAsync filed");
                }
            }
            MyUser? user1 = await userMger.FindByNameAsync("mas");
            if (user1 == null)
            {
                user1 = new MyUser { UserName = "mas" };
                var result = await userMger.CreateAsync(user1, "123456");
                if (!result.Succeeded)
                {
                    return BadRequest("userMger CreateAsync filed");
                }
            }
            if (!await userMger.IsInRoleAsync(user1, "mas"))
            {
                var result = await userMger.AddToRoleAsync(user1, "admin");
                if (!result.Succeeded)
                {
                    return BadRequest("userMger AddToRoleAsync filed");
                }
            }
            return "ok";
        }

(6) Detect logged in user information

[HttpPost]
        public async Task<ActionResult> CheckPwd(CheckPwdRequire require)
        {
            string username = require.User;
            string password = require.Password;
            var sue = await userMger.FindByNameAsync(username);
            if (sue == null)
            {
                if (webHostEnvironment.IsDevelopment())
                {
                    return BadRequest("Username input error");
                }
                else
                {
                    return BadRequest(); // safer
                }
            }
            if (await userMger.IsLockedOutAsync(sue))
            {
                return BadRequest("The user is locked, the lock end time is " + sue.LockoutEnd);
            }
            if (await userMger.CheckPasswordAsync(sue, password))
            {
                await userMger.ResetAccessFailedCountAsync(sue); //Recount lock if login is successful
                return Ok("Login successful");
            }
            else
            {
                //Record login failure
                await userMger.AccessFailedAsync(sue);
                return BadRequest("Username or password is wrong");
            }
        }

(7) Implement the function of resetting password

《1》Password reset and send verification code:

/// <summary>
        /// User password reset
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult> RestPassword(string userName)
        {
            var user = await userMger.FindByNameAsync(userName);
            if (user == null)
            {
                return BadRequest("The user does not exist!");
            }
            string token = await userMger.GeneratePasswordResetTokenAsync(user);
            Console.WriteLine($"The verification code is {token}");
            return Ok();
        }

《2》Get the 6-digit verification code in the console and change the password

 /// <summary>
        /// Input and reset of verification code
        /// </summary>
        /// <param name="name"></param>
        /// <param name="token"></param>
        /// <param name="newPassword"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult> RestPasswordnumber(string UserName, string token, string newPassword)
        {
            var user = await userMger.FindByNameAsync(UserName);
            if (user == null)
            {
                return BadRequest("The user does not exist!");
            }
            var result = await userMger.ResetPasswordAsync(user, token, newPassword);
            if (result.Succeeded)
            {
                await userMger.ResetAccessFailedCountAsync(user);
                return Ok("Password reset successful");
            }
            else
            {
                await userMger.AccessFailedAsync(user);
                return Ok("Password reset failed");
            }
        }

JWT module: Save the login information (token) on the client. The JWT generated by the server is mainly divided into three parts, a header (algorithm signature), a payload (the login information you need to pass), and signature (header + payload + Signature + server-side key) to the browser and save it. When the user logs in, the client sends the JWT entered by the user to the server, and the server compares the saved and received algorithm signatures with the signature submitted by the user. If consistent, remove the user information from the payload.

(1) Install Microsoft.AspNetCore.Authentication.JwtBearer

//Add configuration class
public class JWTsetting
    {
        //key value
        public string? Seckey { get; set; }
        //Expiration
        public int ExpireScondes { get; set; }
    }
//Configure JWT service in program
builder.Services.Configure<JWTsetting>(builder.Configuration.GetSection("JWT"));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>
{
    var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTsetting>();
    byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.Seckey);
    var seckey = new SymmetricSecurityKey(keyBytes);
    opt.TokenValidationParameters = new()
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = seckey
    };
});
//Configure in json configuration class
  "JWT": {
    "Seckey":"dandjsndkajndksaj1da51d5s",
    "ExpireScondes": 3600
  }

The second step (and prepare the above Identity framework in advance):

//Add the following in front of app.UseAuthorization();:
app.UseAuthentication();
//Add a button to Swagger//to add request headers to other login verification requests.
builder.Services.AddSwaggerGen(c =>
{
    var scheme = new OpenApiSecurityScheme()
    {
        Description = "Authorization header \r\
Example:'Bearer 123456abcdef'",
        Reference = new OpenApiReference
        {
            Type = ReferenceType.SecurityScheme,
            Id = "Authorization"
        },
        Scheme = "oauth2",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
    };
    c.AddSecurityDefinition("Authorization", scheme);
    var requirement = new OpenApiSecurityRequirement();
    requirement[scheme] = new List<string>();
    c.AddSecurityRequirement(requirement);
});
//Inject the required classes and interfaces into the controller
private readonly UserManager<MyUser> userManager;
        private readonly RoleManager<MyRole> roleMger;
        //Determine whether the current environment is a development environment
        private readonly IWebHostEnvironment webHostEnvironment;
        //Get key
        private readonly IOptionsSnapshot<JWTsetting> jwtsnapshot;

        public DemoController(UserManager<MyUser> userManager, RoleManager<MyRole> roleMger, IWebHostEnvironment webHostEnvironment, IOptionsSnapshot<JWTsetting> jwtsnapshot)
        {
            this.userManager = userManager;
            this.roleMger = roleMger;
            this.webHostEnvironment = webHostEnvironment;
            this.jwtsnapshot = jwtsnapshot;
        }

Add user:

/// <summary>
        /// Determine whether there are roles and users, and add them
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult<string>> Test1(string username,string password,string roleName)
        {
   
            if (await roleMger.RoleExistsAsync("admin") == false)
            {
                MyRole myRole = new MyRole { Name = "admin" };
                var result = await roleMger.CreateAsync(myRole);
                if (!result.Succeeded)
                {
                    return BadRequest("roleMger CreateAsync filed");
                }
            }
            //Create user using identity
            MyUser myUser = new MyUser();
            myUser.UserName = username;
            MyUser? user1 = await userManager.FindByNameAsync(username);
            if (user1 == null)
            {
                var result=await userManager.CreateAsync(myUser, password);
                if (!result.Succeeded) {
                    return BadRequest("User creation failed");
                }
            }
            //Add a role to the created user
            var user = await userManager.FindByNameAsync(username);
            if (user == null)
            {
                return BadRequest("User does not exist");
            }
            else
            {
                var result = await userManager.AddToRoleAsync(user, roleName);
                if (result.Succeeded)
                {
                    return BadRequest("userMger AddToRoleAsync success");
                }
                else
                {
                    return BadRequest("userMger AddToRoleAsync filed");
                }
            }
        }

Step 3 (Login Verification):

/// Login verification
        [HttpPost]
        public async Task<ActionResult<string>> Login(string username, string password)
        {
            var sue = await userManager.FindByNameAsync(username);
            if (sue == null)
            {
                if (webHostEnvironment.IsDevelopment())
                {
                    return BadRequest("Username input error");
                }
                else
                {
                    return BadRequest(); // safer
                }
            }
            //Account locked due to too many errors
            if (await userManager.IsLockedOutAsync(sue))
            {
                return BadRequest("The user is locked, the lock end time is " + sue.LockoutEnd);
            }
            //Detect user and password
            if (await userManager.CheckPasswordAsync(sue, password))
            {
                await userManager.ResetAccessFailedCountAsync(sue);//If the login is successful, recount the lock
                //Set jwt to load those things
                var claims = new List<Claim>();
                claims.Add(new Claim(ClaimTypes.NameIdentifier, sue.Id.ToString()));
                claims.Add(new Claim(ClaimTypes.Name, sue.UserName));
                //Set role access permissions
                var roles = await userManager.GetRolesAsync(sue);
                foreach (string role in roles)
                {
                    claims.Add(new Claim(ClaimTypes.Role, role));
                }

                //Read the key and expiration time in the configuration
                string key = jwtsnapshot.Value.Seckey;
                DateTime expire = DateTime.Now.AddSeconds(jwtsnapshot.Value.ExpireScondes);
                //Generate JWT to client
                byte[] secBytes = Encoding.UTF8.GetBytes(key);
                var seckey = new SymmetricSecurityKey(secBytes);
                var credentials = new SigningCredentials(seckey, SecurityAlgorithms.HmacSha256Signature);
                var tokenDescriptor = new JwtSecurityToken(claims: claims, expires: expire, signingCredentials: credentials);
                string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
                return jwt;
            }
            else
            {
                //Record login failure
                await userManager.AccessFailedAsync(sue);
                return BadRequest("Username or password is wrong");
            }
        }

The fourth step (if you want to log in using other login methods) is as follows:

[Authorize]//With this feature, this class requires verification when operating.
    public class Demo2Controller : ControllerBase
    {
        [HttpGet]
        public string Text1()
        {
            //Get the value in the message header
          var claim=this.User.FindFirst(ClaimTypes.Name);
            return "ok" + claim.Value;//This will get the username of the currently logged in user
        }
        [HttpPost]
        [AllowAnonymous]//Add this and it will not be verified
        public string Text2()
        {
            return "66666";
        }
        [HttpGet]
        [Authorize(Roles="admin")]
        public string Text3()
        {
            return "3333";
        }
    }

Need to add when logging in and verifying

//The method of adding message header is as follows: Bearer + space + generated jwt
Bearer eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA 1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoibmloYW8 iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJhZG1pbiIsImV4cCI6MTY5NTM3MTU1Nn0.SH3ZMTHUQsna18bt2KfRhrgaOmW_ qHSThr8uw8cqkiI

On the jwt generated by the login account, the message header.