In modern web application development, building secure and scalable APIs is a top priority. ASP.NET Core 8 continues to lead the way by offering powerful features, improved performance, and long-term support for developers. Coupled with Entity Framework Core for data access and JWT (JSON Web Tokens) for stateless authentication, you can create robust REST APIs ready for production environments.
In this tutorial, you’ll learn how to build a secure REST API using ASP.NET Core 8, Entity Framework Core, and JWT Authentication. We’ll walk through the essential steps—from project setup and database modeling to implementing authentication and securing API endpoints. By the end, you'll have a fully working API with user registration, login, and protected routes.
Whether you're a .NET beginner or looking to upgrade your skills to the latest version, this tutorial will provide a comprehensive guide to best practices in API development with .NET 8.
1. Prerequisites
Before we begin, ensure you have the following tools and environment set up:
-
.NET SDK 8.0
-
Visual Studio 2022 (with ASP.NET and web development workload) or Visual Studio Code with C# extension
-
PostgreSQL or SQL Server (we’ll use PostgreSQL in this tutorial)
-
Postman or any other API testing tool
-
Basic knowledge of C#, ASP.NET Core, and RESTful APIs
2. Setting Up the ASP.NET Core 8 Web API Project
Let’s start by creating a new ASP.NET Core Web API project using the .NET CLI:
dotnet new webapi -n SecureApiJwtAuth
cd SecureApiJwtAuth
This command creates a new folder SecureApiJwtAuth
with a Web API template including a WeatherForecastController
.
You can remove the default controller since we’ll create our own later:
rm Controllers/WeatherForecastController.cs
If you're using Visual Studio, you can create the project through the GUI by selecting ASP.NET Core Web API and checking the box for Use controllers (uncheck minimal API).
Project Structure (after setup)
SecureApiJwtAuth/
├── Controllers/
├── Models/
├── Data/
├── Services/
├── DTOs/
├── Program.cs
├── appsettings.json
└── ...
We’ll organize the code using a clean architecture: separating models, data access, services, and controllers.
3. Installing Required NuGet Packages
To build our secure API with Entity Framework Core and JWT authentication, we’ll need a few additional NuGet packages. Run the following commands from the root of your project:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 8.0.7
dotnet add package System.IdentityModel.Tokens.Jwt
dotnet add package BCrypt.Net-Next
✅ If you're using SQL Server instead of PostgreSQL, replace the PostgreSQL package with:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Explanation of the Packages
-
Microsoft.EntityFrameworkCore
,Design
, andTools
: For working with EF Core and managing migrations. -
Npgsql.EntityFrameworkCore.PostgreSQL
: EF Core provider for PostgreSQL. -
Microsoft.AspNetCore.Authentication.JwtBearer
: Enables JWT bearer authentication in ASP.NET Core. -
System.IdentityModel.Tokens.Jwt
: Provides types for creating and validating JWT tokens.
After installing, restore the project:
dotnet restore
4. Configuring the Database with Entity Framework Core
Let’s configure our PostgreSQL database connection in the appsettings.json
file.
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=SecureApiDb;Username=your_user;Password=your_password"
},
"Jwt": {
"Key": "your_super_secret_key_here",
"Issuer": "DjamwareSecureAPI",
"Audience": "DjamwareUsers",
"ExpiresInMinutes": 60
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
🔐 Replace
"your_super_secret_key_here"
with a secure key (you can generate a base64 string for this) and update your database credentials.
psql postgres -U djamware
create database SecureApiDb;
\q
Then in Program.cs
, add EF Core and JWT configuration:
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using SecureApiJwtAuth.Data;
var builder = WebApplication.CreateBuilder(args);
// Add DbContext
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
// Add JWT Authentication
var jwtSettings = builder.Configuration.GetSection("Jwt");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"]))
};
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
5. Creating Models and the DbContext
We'll start by creating a simple user model and the corresponding DbContext class.
Create a Models
folder and add the User.cs
file:
using System.ComponentModel.DataAnnotations;
namespace SecureApiJwtAuth.Models
{
public class User
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(100)]
public required string Username { get; set; }
[Required]
public required string PasswordHash { get; set; }
[MaxLength(50)]
public string Role { get; set; } = "User";
}
}
🔒 Note: We will store the password as a hashed value using a secure hashing algorithm later.
Create a Data
folder and add the AppDbContext.cs
file:
using Microsoft.EntityFrameworkCore;
using SecureApiJwtAuth.Models;
namespace SecureApiJwtAuth.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
}
Add Initial Migration and Update the Database
Before we run the migration, make sure EF Core CLI tools are installed:
dotnet tool install --global dotnet-ef
Then run the following commands to create and apply the migration:
dotnet ef migrations add InitialCreate
dotnet ef database update
This will create the SecureApiDb
PostgreSQL database with a Users
table.
6. Implementing the User Registration and Login APIs
We'll implement two endpoints:
-
POST /api/auth/register
– Register a new user -
POST /api/auth/login
– Authenticate and receive a JWT token
✅ Step 1: Create DTOs
Create a folder DTOs/
and add these two files:
DTOs/RegisterDto.cs:
namespace SecureApiJwtAuth.DTOs
{
public class RegisterDto
{
public required string Username { get; set; }
public required string Password { get; set; }
}
}
DTOs/LoginDto.cs:
namespace SecureApiJwtAuth.DTOs
{
public class LoginDto
{
public required string Username { get; set; }
public required string Password { get; set; }
}
}
✅ Step 2: Create the Auth Controller
Create a new file Controllers/AuthController.cs
:
// Controllers/AuthController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using SecureApiJwtAuth.Data;
using SecureApiJwtAuth.DTOs;
using SecureApiJwtAuth.Models;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using BCrypt.Net;
namespace SecureApiJwtAuth.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly AppDbContext _context;
private readonly IConfiguration _configuration;
public AuthController(AppDbContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
}
[HttpPost("register")]
public async Task<IActionResult> Register(RegisterDto dto)
{
if (await _context.Users.AnyAsync(u => u.Username == dto.Username))
return BadRequest("Username already exists.");
var user = new User
{
Username = dto.Username,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(dto.Password)
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
return Ok("User registered successfully.");
}
[HttpPost("login")]
public async Task<IActionResult> Login(LoginDto dto)
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.Username == dto.Username);
if (user == null || !BCrypt.Net.BCrypt.Verify(dto.Password, user.PasswordHash))
return Unauthorized("Invalid username or password.");
var token = GenerateJwtToken(user);
return Ok(new { token });
}
private string GenerateJwtToken(User user)
{
var jwtSettings = _configuration.GetSection("Jwt");
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Username),
new Claim(ClaimTypes.Role, user.Role),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(
issuer: jwtSettings["Issuer"],
audience: jwtSettings["Audience"],
claims: claims,
expires: DateTime.UtcNow.AddMinutes(double.Parse(jwtSettings["ExpiresInMinutes"])),
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
}
✅ Step 3: Test the Endpoints
Use Postman or curl to test:
Register:
POST http://localhost:5000/api/auth/register
Content-Type: application/json
{
"username": "djamware",
"password": "password123"
}
Login:
POST http://localhost:5000/api/auth/login
Content-Type: application/json
{
"username": "djamware",
"password": "password123"
}
Expected response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
7. Setting Up JWT Authorization and Protecting Routes
We already configured JWT authentication in Program.cs
, so now we’ll:
-
Apply
[Authorize]
attributes to secure endpoints -
Test token-protected access
✅ Step 1: Add Microsoft.AspNetCore.Authorization
Ensure you have the correct using directive in your controller:
using Microsoft.AspNetCore.Authorization;
✅ Step 2: Create a Sample Protected Controller
Let’s add a new controller to test the JWT protection.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace SecureApiJwtAuth.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ProtectedController : ControllerBase
{
[HttpGet("public")]
public IActionResult Public()
{
return Ok("This is a public endpoint.");
}
[Authorize]
[HttpGet("secure")]
public IActionResult Secure()
{
var username = User.Identity?.Name ?? "Unknown";
return Ok($"Welcome, {username}. You’ve accessed a protected route!");
}
[Authorize(Roles = "Admin")]
[HttpGet("admin")]
public IActionResult AdminOnly()
{
return Ok("You are an admin!");
}
}
}
✅ Step 3: Test with Postman
1. Access Public Route
GET http://localhost:5000/api/protected/public
✅ Should return:
"This is a public endpoint."
2. Access Secure Route without Token
GET http://localhost:5000/api/protected/secure
⛔ Should return:
401 Unauthorized
3. Access Secure Route with JWT Token
-
First, login via
/api/auth/login
-
Then, in Postman:
-
Select "Authorization" tab
-
Type:
Bearer Token
-
Paste the token
-
GET http://localhost:5000/api/protected/secure
✅ Should return something like:
"Welcome, djamware. You’ve accessed a protected route!"
🔐 The
[Authorize(Roles = "Admin")]
works only if a user is created with"Role": "Admin"
— useful for role-based access control (RBAC).
8. Testing the REST API with Postman
Now that you’ve built a secure API, it's time to test the entire flow — from registration and login to accessing protected routes using JWT tokens.
✅ Step 1: Register a New User
Request:
POST http://localhost:5000/api/auth/register
Content-Type: application/json
Body:
{
"username": "djamware",
"password": "password123"
}
✅ Expected Response:
"User registered successfully."
✅ Step 2: Login to Get a JWT Token
Request:
POST http://localhost:5000/api/auth/login
Content-Type: application/json
Body:
{
"username": "djamware",
"password": "password123"
}
✅ Expected Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
✅ Step 3: Access a Protected Endpoint
Request:
GET http://localhost:5000/api/protected/secure
Authorization: Bearer <paste_token_here>
✅ Expected Response:
"Welcome, djamware. You’ve accessed a protected route!"
If you skip the token or use an invalid one, you’ll get a 401 Unauthorized
.
✅ Step 4: Test Admin Route (Optional)
If you’ve created a user with Role = "Admin"
manually or via DB seed, test:
GET http://localhost:5000/api/protected/admin
Authorization: Bearer <admin_token>
✅ Step 5: Use Swagger for Visual Testing
Make sure Swagger UI is enabled in Program.cs
(already done earlier):
app.UseSwagger();
app.UseSwaggerUI();
Run the app and open:
https://localhost:5001/swagger
🔐 To access protected routes in Swagger, click Authorize (lock icon), paste your
Bearer <token>
string, and proceed with testing authenticated endpoints.
9. Conclusion and Next Steps
In this tutorial, you’ve built a secure REST API using ASP.NET Core 8, Entity Framework Core, and JWT authentication from the ground up. You learned how to:
-
Set up a new ASP.NET Core Web API project
-
Configure a PostgreSQL database with Entity Framework Core
-
Implement secure user registration and login with hashed passwords
-
Generate and validate JWT tokens
-
Protect API endpoints using role-based authorization
-
Test the API using Postman and Swagger UI
This foundation is production-ready and can be extended to support:
-
Refresh tokens and token expiration handling
-
Role management and permissions
-
Account verification and password reset flows
-
Deployment to cloud platforms like Azure, AWS, or Docker
ASP.NET Core 8 brings modern features, performance, and long-term support, making it an excellent choice for secure backend development.
You can get the full source code on our GitHub.
That's just the basics. If you need more deep learning about ASP.NET Core, Angular, or related, you can take the following cheap course:
- ANGULAR and ASP. NET Core REST API - Real World Application
- Creating GraphQL APIs with ASP. Net Core for Beginners
- ASP .Net MVC Quick Start
- Master SignalR: Build Real-Time Web Apps with ASP. NET
- Fullstack Asp. Net Core MVC & C# Bootcamp With Real Projects
- ASP. NET Core MVC - A Step by Step Course
Thanks!