Here is a step-by-step guide on how to create a .NET Core Web API project and implement JWT (JSON Web Token) from scratch in .NetCore version 8.
Project folder structure looks like below image.
Add the necessary packages for Identity and Entity Framework:
Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Right-click on solution and add Data folder (create one if it doesn’t exist) and add a new class named ApplicationDbContext.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;
namespace GenerateToken.Data
{
public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
}
Open the Program.cs file and add the following configurations and add the UseAuthentication() and UseAuthorization() methods.
using JswPoc;
using JswPoc.BLL.Middlewares;
using JswPoc.BLL.ServiceExtentions;
using JswPoc.BLL.Services;
using JswPoc.Data;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
//builder.Services.AddIdentity<IdentityUser, IdentityRole>()
// .AddEntityFrameworkStores<ApplicationDbContext>()
// .AddDefaultTokenProviders();
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true
};
});
builder.Services.AddAuthorization();
builder.Services.AddSwaggerGen();
builder.Services.AddControllersWithViews();
//Custom Services
builder.Services.AddCustomServices();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
using var scope = builder.Services.BuildServiceProvider();
await SeedData.InitializeAsync(scope);
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
//app.MapRazorPages();
app.Run();
Open the appsettings.json file and add the connection string to the database.
{
"ConnectionStrings": {
"DefaultConnection": "Server=DESKTOP-V6NQ38C\\SQLEXPRESS;Database=JWTTokenTest;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Jwt": {
"Key": "v3ryS3cur3R@nd0mK3y!f0rHmacSha256geeecvcy4y4y32y234343rgrt434t5t43ffdsffef",
"Issuer": "https://localhost:5067/",
"Audience": "https://localhost:5067/"
},
"AllowedHosts": "*"
}
PM> Add-Migration InitialCreate
PM> Update-Database
Now run the command.
Now Open the SQL Server database and check it will create automatically database and all tables.
Open the Controllers folder and add an AccountController.cs. The AccountController class in your code is an ASP.NET Core MVC controller that handles user authentication and role management functionality. Below is a detailed breakdown of its main functions:
private readonly IConfiguration configuration;
private readonly UserManager _userManager;
private readonly SignInManager _signInManager;
private readonly RoleManager _roleManager;
public AccountController(IConfiguration configuration, UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, RoleManager<IdentityRole> roleManager)
{
this.configuration = configuration;
_userManager = userManager;
_signInManager = signInManager;
_roleManager = roleManager;
}
This controller relies on four injected services:IConfiguration
: Used to access
configuration settings like JWT options (Issuer, Audience, Key) from
appsettings.json.UserManager<IdentityUser>
: Manages
user-related operations like creating users, finding users by email, etc.SignInManager<IdentityUser>
: Manages
sign-in operations like password sign-in, signing in a user, or signing out.RoleManager<IdentityRole>
: Manages
role-related operations like creating and assigning roles.2. User Registration (Register Methods)
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
return View(model);
}
3. User Login (Login Methods)
[HttpGet]
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task Login([FromBody] LoginViewModel model)
{
if (ModelState.IsValid)
{
var result = await this._signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
var UserName= model.Email;
var user = await this._userManager.FindByNameAsync(UserName);
if (user != null)
{
var roles = await this._userManager.GetRolesAsync(user);
var issuer = this.configuration["Jwt:Issuer"];
var audience = this.configuration["Jwt:Audience"];
var key = Encoding.ASCII.GetBytes(this.configuration["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim("Id", Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Name, UserName),
new Claim(ClaimTypes.Role, roles.FirstOrDefault()!),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}),
Expires = DateTime.UtcNow.AddMinutes(5),
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var stringToken = tokenHandler.WriteToken(token);
return Ok(new { Token = stringToken });
}
}
}
return Unauthorized("Invalid Credentials.");
}
5. Role Management (CreateRole and AssignRoleToUser Methods)
[HttpPost]
public async Task<IActionResult> CreateRole(string roleName)
{
if (!string.IsNullOrWhiteSpace(roleName))
{
var roleExists = await _roleManager.RoleExistsAsync(roleName);
if (!roleExists)
{
var role = new IdentityRole { Name = roleName };
await _roleManager.CreateAsync(role);
ViewBag.Message = $"Role '{roleName}' created successfully.";
}
else
{
ViewBag.Message = $"Role '{roleName}' already exists.";
}
}
return View();
}
[HttpPost]
public async Task AssignRoleToUser(string email, string roleName)
{
if (!string.IsNullOrWhiteSpace(email) && !string.IsNullOrWhiteSpace(roleName))
{
var user = await _userManager.FindByEmailAsync(email);
if (user != null)
{
if (await _roleManager.RoleExistsAsync(roleName))
{
if (!await _userManager.IsInRoleAsync(user, roleName))
{
var result = await _userManager.AddToRoleAsync(user, roleName);
if (result.Succeeded)
{
ViewBag.Message = $"Role '{roleName}' assigned to user '{email}' successfully.";
}
else
{
ViewBag.Message = "Error occurred while assigning role to the user.";
}
}
else
{
ViewBag.Message = $"User '{email}' is already in role '{roleName}'.";
}
}
else
{
ViewBag.Message = $"Role '{roleName}' does not exist.";
}
}
else
{
ViewBag.Message = $"User with email '{email}' not found.";
}
}
else
{
ViewBag.Message = "Email and role name cannot be empty.";
}
return View();
}
Now add the above code in AccountController.cs
using GenerateToken.Models;
using GenerateToken.BLL.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace AuthDemoApp.Controllers
{
public class AccountController : Controller
{
private readonly IConfiguration configuration;
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly RoleManager<IdentityRole> _roleManager;
public AccountController(IConfiguration configuration, UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, RoleManager<IdentityRole> roleManager)
{
this.configuration = configuration;
_userManager = userManager;
_signInManager = signInManager;
_roleManager = roleManager;
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
return View(model);
}
[HttpGet]
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginViewModel model)
{
if (ModelState.IsValid)
{
var result = await this._signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
var UserName= model.Email;
var user = await this._userManager.FindByNameAsync(UserName);
if (user != null)
{
var roles = await this._userManager.GetRolesAsync(user);
var issuer = this.configuration["Jwt:Issuer"];
var audience = this.configuration["Jwt:Audience"];
//var key = Encoding.ASCII.GetBytes(this.configuration["Jwt:Key"]);
var key = Encoding.ASCII.GetBytes(this.configuration["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim("Id", Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Name, UserName),
new Claim(ClaimTypes.Role, roles.FirstOrDefault()!),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}),
Expires = DateTime.UtcNow.AddMinutes(5),
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwtToken = tokenHandler.WriteToken(token);
var stringToken = tokenHandler.WriteToken(token);
return Ok(new { Token = stringToken });
}
}
//ModelState.AddModelError(string.Empty, "Invalid login attempt.");
}
return Unauthorized("Invalid Credentials.");
}
[HttpPost]
public async Task Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Index", "Home");
}
// Role management
[HttpPost]
public async Task CreateRole(string roleName)
{
if (!string.IsNullOrWhiteSpace(roleName))
{
var roleExists = await _roleManager.RoleExistsAsync(roleName);
if (!roleExists)
{
var role = new IdentityRole { Name = roleName };
await _roleManager.CreateAsync(role);
ViewBag.Message = $"Role '{roleName}' created successfully.";
}
else
{
ViewBag.Message = $"Role '{roleName}' already exists.";
}
}
return View();
}
[HttpPost]
public async Task<IActionResult> AssignRoleToUser(string email, string roleName)
{
// Check if both email and roleName are provided
if (!string.IsNullOrWhiteSpace(email) && !string.IsNullOrWhiteSpace(roleName))
{
// Find the user by email
var user = await _userManager.FindByEmailAsync(email);
if (user != null)
{
// Check if the role exists
if (await _roleManager.RoleExistsAsync(roleName))
{
// Check if the user is already in the role
if (!await _userManager.IsInRoleAsync(user, roleName))
{
// Assign the role to the user
var result = await _userManager.AddToRoleAsync(user, roleName);
if (result.Succeeded)
{
ViewBag.Message = $"Role '{roleName}' assigned to user '{email}' successfully.";
}
else
{
ViewBag.Message = "Error occurred while assigning role to the user.";
}
}
else
{
ViewBag.Message = $"User '{email}' is already in role '{roleName}'.";
}
}
else
{
ViewBag.Message = $"Role '{roleName}' does not exist.";
}
}
else
{
ViewBag.Message = $"User with email '{email}' not found.";
}
}
else
{
ViewBag.Message = "Email and role name cannot be empty.";
}
return View();
}
}
}
Run the application and test in Postman.
Now open SQL Server JWTTokenTest Database and check registred data.
Now test create role in postman
Now open SQL Server JWTTokenTest Database and check role in AspNetRoles table.
Now test AssignRoleToUser in postman
Now open SQL Server JWTTokenTest Database and check assign role in AspNetUserRoles table.
Now test login and create Token in postman
You can also download full code to understand more better way. Please follow the below link.
Download Generate Token Sample code