Introduction
Authentication is one of the most important features in any application. Users need to register, login, and access resources securely.
ASP.NET Core Identity provides a ready-made solution for:
User Management
Password Hashing
Role Management
Claims Management
Authorization
Security Features
Project Structure
A simple project structure:
DotNetIdentity
│
├── Controllers
│ └── UserController.cs
│
├── Data
│ ├── TestDbContext.cs
│ └── Entity
│ └── ApplicationUser.cs
│
├── Model
│ ├── JWTSettings.cs
│ └── DTO
│ └── UserDto.cs
│
├── Program.cs
│
└── appsettings.json
Required NuGet Packages
Install the following packages.
Identity Package
Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore
Purpose:
IdentityUser
IdentityRole
UserManager
RoleManager
IdentityDbContext
Entity Framework Core SQL Server
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Purpose:
EF Core Migration Tools
Install-Package Microsoft.EntityFrameworkCore.Tools
Purpose:
Add-Migration
Update-Database
JWT Authentication Package
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Purpose:
JWT Validation
Bearer Authentication
Swagger Package
Install-Package Swashbuckle.AspNetCore
Purpose:
API Documentation
JWT Testing
Step 1: Create JWT Settings Model
File Location
Model/JWTSettings.cs
Code
namespace DotNetIdentity.Model
{
public class JWTSettings
{
public string Issuer { get; set; } = string.Empty;
public string Audience { get; set; } = string.Empty;
public string SecretKey { get; set; } = string.Empty;
public int ExpireInMinutes { get; set; }
}
}
Purpose
This class stores JWT configuration values from appsettings.json.
Step 2: Configure appsettings.json
File Location
appsettings.json
Code
{
"ConnectionStrings": {
"DefaultConnection": "Server=YourMSSqlServerName;Initial Catalog=TestDbIdentity;Integrated Security=true;TrustServerCertificate=true;"
},
"JWTSettings": {
"Issuer": "DemoApp",
"Audience": "UserApp",
"SecretKey": "#@493JDSSWOP()Y*&^&%^R$%R^&*@@#!@",
"ExpireInMinutes": 20
}
}
Purpose
Stores:
Step 3: Create Custom User
File Location
Data/Entity/ApplicationUser.cs
Code
using Microsoft.AspNetCore.Identity;
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
}
Why Create ApplicationUser?
IdentityUser already contains:
Id
Email
UserName
PasswordHash
PhoneNumber
ApplicationUser allows us to add extra fields like:
Step 4: Configure Identity in Program.cs
File Location
Program.cs
Identity Registration
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireDigit = false;
})
.AddEntityFrameworkStores<TestDbContext>();
Purpose
Registers:
UserManager
RoleManager
Identity Services
Configures password rules.
Step 5: Configure Database
Program.cs
builder.Services.AddDbContext<TestDbContext>(options =>
{
options.UseSqlServer(
builder.Configuration["ConnectionStrings:DefaultConnection"]);
});
Purpose
Connects Identity to the SQL Server database.
Step 6: Configure JWT Authentication
Program.cs
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme =
JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme =
JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = settings.Issuer,
ValidateAudience = true,
ValidAudience = settings.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey =
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(
settings.SecretKey))
};
});
Purpose
Validates incoming JWT tokens.
Checks:
Step 7: Configure Swagger Authentication
Program.cs
options.AddSecurityDefinition("Bearer",
new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
Purpose
Adds the Authorize button inside Swagger UI.
Step 8: Create User API
File Location
Controllers/UserController.cs
Create User Endpoint
[HttpPost("create-user")]
public async Task<IActionResult> CreateUser(UserDto userData)
{
var appUser = new ApplicationUser()
{
FirstName = userData.FirstName,
LastName = userData.LastName,
Email = userData.Email,
PhoneNumber = userData.Phone,
UserName = userData.Email
};
var result =
await _userManager.CreateAsync(
appUser,
userData.Password);
if (!result.Succeeded)
{
return BadRequest(result.Errors);
}
return Ok("User Created");
}
What Happens Here?
User sends data.
ApplicationUser object is created.
Identity hashes the password.
User gets stored in the AspNetUsers table.
Step 9: Login API
Login Endpoint
[HttpPost("login-user")]
public async Task<IActionResult>
LoginUser(string email, string password)
{
var user =
await _userManager.FindByEmailAsync(email);
if (user == null)
return BadRequest();
var result =
await _userManager
.CheckPasswordAsync(user, password);
if (!result)
return BadRequest();
// Generate JWT
}
What Happens?
Email searched in the database.
Password checked against PasswordHash.
If valid, a JWT token is generated.
Step 10: Generate JWT Token
Login Method
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Email,email),
new Claim(ClaimTypes.Role,"admin") //hardcoded 'admin' role here
};
Create Token
var tokenObj = new JwtSecurityToken(
claims: claims,
issuer: jwtsettings.Issuer,
audience: jwtsettings.Audience,
expires: DateTime.UtcNow.AddMinutes(
jwtsettings.ExpireInMinutes),
signingCredentials:
new SigningCredentials(
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(
jwtsettings.SecretKey)),
SecurityAlgorithms.HmacSha256)
);
// Note: The token currently includes a hardcoded role ("admin"), so it will only authorize users as admin.
// In real-world applications, roles should not be hardcoded. Instead, they should be retrieved from the user's
// assigned roles in the database using Identity (e.g., _userManager.GetRolesAsync(user)).
Convert to String
var token =
new JwtSecurityTokenHandler()
.WriteToken(tokenObj);
Return Token
return Ok(new
{
token
});
Step 11: Protect APIs
Admin API
[Authorize(Roles = "admin")]
[HttpPost("test-auth-admin")]
public IActionResult TestAuthAdmin()
{
return Content(
"Authentication-admin Api Called");
}
Only users with the Admin role can access.
Manager API
[Authorize(Roles = "manager")]
[HttpPost("test-auth-manager")]
public IActionResult TestAuthManager()
{
return Content(
"Authentication-manager Api Called");
}
Only users with the Manager role are allowed.
Step 12: Middleware Pipeline
Program.cs
app.UseAuthentication();
app.UseAuthorization();
Order is important.
Authentication must come before Authorization.
Step 13: Create Database
Run Migration
Add-Migration InitialIdentity
Apply Migration
Update-Database
Identity automatically creates:
AspNetUsers
AspNetRoles
AspNetUserRoles
AspNetUserClaims
AspNetRoleClaims
AspNetUserTokens
AspNetUserLogins
Testing Flow
Register User
POST
/api/User/create-user
Creates a new user.
Login User
POST
/api/User/login-user
Returns:
{
"token": "eyJhbGc..."
}
Swagger Authorization
Click:
Authorize
Enter:
token here...
Now protected APIs can be called.
Conclusion
In this project we successfully implemented:
Identity handles password hashing and user management, while JWT provides secure stateless authentication for APIs. Together they form the foundation of modern ASP.NET Core authentication systems.