ASP.NET Core  

ASP.NET Core Identity with JWT Authentication - Step-by-Step Practical Guide

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:

  • SQL Server connectivity

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:

  • Database Connection String

  • JWT Secret Key

  • JWT Issuer

  • JWT Audience

  • Token Expiry Time

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:

  • FirstName

  • LastName

  • Address

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:

  • Issuer

  • Audience

  • Secret Key

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:

  • ✅ ASP.NET Core Identity

  • ✅ SQL Server Integration

  • ✅ User Registration

  • ✅ User Login

  • ✅ JWT Authentication

  • ✅ Role-Based Authorization

  • ✅ Swagger JWT Integration

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.