Sharing a login demo case I learned earlier. If there are any shortcomings in the code, please feel free to point them out!
Tools: Developed using VS Code and its plugins for lightweight development while reducing command line typing. No conflicts with VS!


1. Create a WebApi Project via Plugin

2. Download NuGet Packages Required by the Project Using the Plugin

3. Code Writing
① Create a User Entity
/// <summary>
/// Login user entity class, inherits IdentityUser from the Identity framework
/// </summary>
public class AppUser:IdentityUser
{
// Add three additional fields
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public string FullName { get; set; }
}
② Create a DbContext Class
public class AppDBContext : IdentityDbContext<AppUser, IdentityRole, string>
{
public AppDBContext(DbContextOptions options) : base(options)
{
}
}
③ Inject the DbContext in Startup
services.AddDbContext<AppDBContext>(options =>
{
options.UseMySql(Configuration.GetConnectionString("DefaultConnection"), MySqlServerVersion.LatestSupportedServerVersion);
});
// AddEntityFrameworkStores is used to create services between users and passwords
services.AddIdentity<AppUser, IdentityRole>(opt => { }).AddEntityFrameworkStores<AppDBContext>();
④ Generate Database Tables via Code-First in Terminal
dotnet ef migrations add init
dotnet ef database update

⑤ Configure JWT
Configure services in the ConfigureServices method
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// The JWT key needs to be complex
var key = Encoding.ASCII.GetBytes(Configuration["JWTConfig:Key"]);
var issure = Configuration["JWTConfig:Issuer"]; // Issuer
var audience = Configuration["JWTConfig:Audience"]; // Audience
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true, // If set to true, set the ValidIssuer property; otherwise JWT validation will fail
ValidateAudience = true, // Same as above, set the ValidAudience property
RequireExpirationTime = true,
ValidateLifetime=true, // Token expiration buffer time, default 5 minutes; expiration time needs to include this 5-minute buffer
// If ValidateIssuer is set to false, the following two properties are not needed
ValidIssuer = issure,
ValidAudience = audience,
};
});
// For multiple roles, configure as follows. It can be simplified on action methods: [Authorize(Policy ="PolicyGroup")]
services.AddAuthorization(options =>
{
options.AddPolicy("PolicyGroup", policy => policy.RequireRole("Admin", "User"));
});
Use services in the Configure method
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "SwaggerDemo v1"));
}
app.UseHttpsRedirection();
app.UseCors("any");
app.UseRouting();
// Pay attention to the order
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
⑥ Configure Swagger
Configure services in ConfigureServices
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "SwaggerDemo", Version = "v1", Description = "Demo API for showing Swagger" });
// The following two steps configure the "lock" on Swagger
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header, // Located in the Header
Description = "Please enter the token directly here without adding 'Bearer' and then a space",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement{
{
new OpenApiSecurityScheme{
Reference=new OpenApiReference{
Type=ReferenceType.SecurityScheme,
Id="Bearer"
}
},Array.Empty<string>()
}
});
// Display Swagger interface comments
// Note: VS Code users need to manually configure the project's csproj file to generate comment XML files
// See the PropertyGroup in the project file for details
var fileName = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var filePath = Path.Combine(AppContext.BaseDirectory, fileName);
c.IncludeXmlComments(filePath);
});
Use services in Configure
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// Use Swagger middleware
app.UseSwagger();
// The "v1" here must match the one in c.SwaggerDoc("v1") above
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "SwaggerDemo v1"));
}
app.UseHttpsRedirection();
app.UseCors("any");
app.UseRouting();
// Pay attention to the order
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
⑦ Create UserController and Inject Login Services via Constructor
private readonly UserManager<AppUser> _userManger; // User service
private readonly SignInManager<AppUser> _signInManger; // Login service
private readonly RoleManager<IdentityRole> _roleManger; // Role service
private readonly JWTConfig _jwtConfig; // Configuration framework injects configuration into entity class
public UserController(ILogger<UserController> logger, UserManager<AppUser> userManager,
SignInManager<AppUser> signInManager, IOptions<JWTConfig> jwtConfig, RoleManager<IdentityRole> roleManger)
{
this._logger = logger;
this._userManger = userManager;
this._signInManger = signInManager;
this._jwtConfig = jwtConfig.Value;
this._roleManger = roleManger;
}
Register User
/// <summary>
/// User Registration
/// AddAndUpdateUserrRegisterModel is a DTO to receive the object
/// AllowAnonymous: no permission verification required
/// Author: xxxx
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("RegisterUser")]
public async Task<Object> RegisterUser(AddAndUpdateUserrRegisterModel model)
{
try
{
// Check if roles are included during registration
if (model.Roles is null || model.Roles.Count <= 0)
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Error, "Role cannot be empty"));
}
// Loop to check if the roles the user registers for exist. Method for creating roles: AddRole()
foreach (var item in model.Roles)
{
if (!await _roleManger.RoleExistsAsync(item))
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Error, "The role does not exist"));
}
}
// Create a user object
var user = new AppUser()
{
UserName = model.Email,
FullName = model.FullName,
Email = model.Email,
DateCreated = DateTime.Now,
DateModified = DateTime.UtcNow
};
// Register user
var result = await _userManger.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// After successful registration, get the temporarily created user
var tempUser = await _userManger.FindByEmailAsync(model.Email);
// Loop to assign roles to the created user
foreach (var role in model.Roles)
{
await _userManger.AddToRoleAsync(tempUser, role); // Add role
}
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Ok, "User successfully registered!", null));
}
// Return if user creation failed
return await Task.FromResult(string.Join(",", result.Errors.Select(x => x.Description).ToArray()));
}
catch (System.Exception ex)
{
// Return exception
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Error, ex.Message, null));
}
}
Login
/// <summary>
/// Generate Token
/// Author: xxxx
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
private string GenarateToken(AppUser user, List<string> roles)
{
var jwtTokenHandle = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtConfig.Key);
// Configure Subject
var claims = new List<Claim>()
{
new Claim(JwtRegisteredClaimNames.NameId,user.Id),
new Claim(JwtRegisteredClaimNames.Email,user.Email),
new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString())
};
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role,role));
}
var tokenDescriptor = new SecurityTokenDescriptor
{
// Multiple roles
Subject=new ClaimsIdentity(claims),
// Single role
// Subject = new ClaimsIdentity(new[]
// {
// new System.Security.Claims.Claim(JwtRegisteredClaimNames.NameId,user.Id),
// new System.Security.Claims.Claim(JwtRegisteredClaimNames.Email,user.Email),
// new System.Security.Claims.Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString())
// // new System.Security.Claims.Claim(ClaimTypes.Role,"role")
// }),
// Expiration time: 12 hours
Expires = DateTime.UtcNow.AddSeconds(6),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
Audience = _jwtConfig.Audience, // If not configured, it will return UnAuthorized
Issuer = _jwtConfig.Issuer // Same as above
};
// Create token
var token = jwtTokenHandle.CreateToken(tokenDescriptor);
return jwtTokenHandle.WriteToken(token);
}
/// <summary>
/// User Login
/// LoginModel is a DTO for login
/// Author: xxxx
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost]
public async Task<object> Login(LoginModel model)
{
try
{
if (!ModelState.IsValid)
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Error, "Invalid parameters!", null));
}
var result = await _signInManger.PasswordSignInAsync(model.UserName, model.Password, false, false);
if (result.Succeeded)
{
// Get user on success
var appUser = await _userManger.FindByNameAsync(model.UserName);
var roles = (await _userManger.GetRolesAsync(appUser)).ToList();
// await _userManger.GetRolesAsync(appUser);
var user = new UserDto(appUser.FullName, appUser.Email, appUser.UserName, appUser.DateCreated, roles)
{
// Generate Token
Token = GenarateToken(appUser,roles)
};
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Ok, "Login successful", user));
}
else
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Error, "Login failed", null));
}
}
catch (System.Exception ex)
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Error, ex.Message, null));
}
}


Add Role

First, set the token obtained from the user login into Swagger, then access the interface restricted to the Admin role.
/// <summary>
/// Add Role
/// [Authorize(Roles ="Admin")] - Only users with the Admin role can access
/// Author: xxx
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[Authorize(Roles ="Admin")]
[HttpPost("AddRole")]
public async Task<object> AddRole(AddRoleModel model)
{
try
{
if (model is null || string.IsNullOrWhiteSpace(model.Role))
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Error, "Role cannot be empty"));
}
// Check if the role exists in the [AspNetRoles] table
if (await _roleManger.RoleExistsAsync(model.Role))
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Ok, "Role already exists"));
}
var role = new IdentityRole()
{
Name = model.Role,
};
// Create role
var result = await _roleManger.CreateAsync(role);
if (result.Succeeded)
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Ok, "Role created successfully!"));
}
else
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Error, "Role creation failed!"));
}
}
catch (System.Exception)
{
return await Task.FromResult(new ResponseModel(Enums.ResponseCode.Error));
}
}
What if token permission validation fails? Here ASP.NET Core 5.0 introduces a new interface IAuthorizationMiddlewareResultHandler to handle permission validation. See the code below!
/// <summary>
/// This is a new authorization handling failure in ASP.NET Core 5, which can directly expose the request context, making it much easier!!!
/// Author: xxx
/// </summary>
public class AuthorizationHandleMiddleWare : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler authorizationHandleMiddleWare =new();
public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
{
// When the token is invalid or does not exist, authorizeResult.Challenged is true
if(authorizeResult.Challenged)
{
// TODO: After getting the user object from context, you can check the token here to distinguish whether the token is expired
var a=context.Request.Headers["Authorization"];
context.Response.StatusCode=(int)HttpStatusCode.OK;
await context.Response.WriteAsJsonAsync(new ResponseModel(Enums.ResponseCode.UnAuthorized,"You are not authorized, please check if the Token is valid!"));
return ;
}
// If token validation passes but the accessed resource has no permission, authorizeResult.Forbidden is true
if(authorizeResult.Forbidden)
{
context.Response.StatusCode=(int)HttpStatusCode.OK;
await context.Response.WriteAsJsonAsync(new ResponseModel(Enums.ResponseCode.ForBidden,"You do not have permission to access this!"));
return ;
}
await authorizationHandleMiddleWare.HandleAsync(next,context,policy,authorizeResult);
}
}
Additionally, you need to register the service in ConfigureServices:
// .NET 5 new permission validation middleware; inject dependency here, see AuthorizationHandleMiddleWare.cs for details
services.AddSingleton<IAuthorizationMiddlewareResultHandler,AuthorizationHandleMiddleWare>();
The above is a simple demo for login. For detailed code, please visit the repository: https://gitee.com/holyace/together/tree/JarryGu_develop/framework/JwtLoginDemo