In the previous articles, we built a complete JWT authentication flow. Our SecureShop API can now:
Generate JWTs after successful login
Validate incoming JWTs
Protect endpoints using [Authorize]
Implement Role-Based and Policy-Based Authorization
Create Custom Authorization Handlers
However, one important part of our application is still unrealistic.
Our login endpoint still uses hardcoded credentials like this:
if (request.Email != "[email protected]" ||
request.Password != "Admin@123")
{
return Unauthorized();
}
This approach is useful for learning, but no real application stores users in source code.
In this article, we'll replace the hardcoded login with Entity Framework Core so that users can authenticate using records stored in a SQL Server database.
![Replace Hardcoded Login with EF Core Database Authentication in ASP.NET Core Web API]()
Why Replace Hardcoded Login?
Imagine the SecureShop application has thousands of customers.
Every day:
If user information is written directly inside the code, every new customer would require modifying the application and redeploying it.
Instead, user information should be stored in a database.
The authentication flow becomes much more practical.
Login Request
│
▼
ASP.NET Core API
│
▼
SQL Server
│
▼
Find User
│
▼
Generate JWT
This allows the application to authenticate any registered user without changing the code.
How Database Authentication Works
The overall login process remains almost the same.
The only difference is where the user information comes from.
Client
│
│ Email + Password
▼
Login API
│
▼
Entity Framework Core
│
▼
SQL Server
│
▼
User Found?
│
Yes │ No
│
▼
Generate JWT
Notice that JWT generation hasn't changed.
Only the user lookup process is different.
Step 1: Install Entity Framework Core Packages
Install the SQL Server provider.
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
These packages allow Entity Framework Core to communicate with SQL Server and create database migrations.
Step 2: Create the User Entity
Replace our simple model with an Entity Framework entity.
namespace SecureShop.Api.Models;
public class ApplicationUser
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Role { get; set; } = "Customer";
}
For now, the password is stored as plain text only to simplify the learning process.
Step 3: Create the Database Context
Create ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
using SecureShop.Api.Models;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<ApplicationUser> Users => Set<ApplicationUser>();
}
The DbSet<ApplicationUser> represents the Users table in SQL Server.
Step 4: Configure SQL Server
Add the connection string to appsettings.json.
{
"ConnectionStrings": {
"DefaultConnection":
"Server=.;Database=SecureShopDb;Trusted_Connection=True;TrustServerCertificate=True;"
}
}
Now register the database context.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection"));
});
ASP.NET Core can now create and use the database context through Dependency Injection.
Step 5: Create the Database
Create the first migration.
dotnet ef migrations add InitialCreate
Update the database.
dotnet ef database update
Entity Framework Core creates the Users table automatically.
Step 6: Insert Sample Data
For testing, insert a sample user.
Now our application has a real user stored in SQL Server.
Step 7: Update the Login Endpoint
Instead of checking hardcoded values, search the database.
[HttpPost("login")]
public IActionResult Login(LoginRequest request)
{
var user = _context.Users.FirstOrDefault(x =>
x.Email == request.Email &&
x.Password == request.Password);
if (user is null)
{
return Unauthorized();
}
var token = _tokenService.GenerateToken(user);
return Ok(new LoginResponse(
token,
DateTime.UtcNow.AddMinutes(30)));
}
Notice that the login flow hasn't changed.
The only difference is that the user now comes from SQL Server instead of source code.
Internal Authentication Flow
Let's see what happens internally.
Client
│
│ Login Request
▼
AuthController
│
▼
ApplicationDbContext
│
▼
SQL Server
│
▼
Find Matching User
│
├── Not Found → 401 Unauthorized
│
▼
Generate JWT
│
▼
Return Access Token
This is much closer to how authentication works in real applications.
Practical Example
Let's continue with our SecureShop API.
The database contains:
Name : John
Email : [email protected]
Password : Password@123
Role : Customer
The client sends:
POST /api/auth/login
{
"email": "[email protected]",
"password": "Password@123"
}
The API queries SQL Server.
↓
User found.
↓
JWT generated.
↓
Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs..."
}
If the email or password doesn't match any record, the API returns:
401 Unauthorized
Limitations of This Approach
Although database authentication is much better than hardcoded login, there is still one major problem.
We're comparing plain-text passwords.
user.Password == request.Password
This is not secure.
If someone gains access to the database, every user's password becomes visible.
Production applications never store passwords in plain text.
Instead, passwords are stored as secure hashes.
We'll fix this in the next article.
Common Mistakes
Mistake 1: Keeping Hardcoded Credentials
Hardcoded usernames and passwords should only be used for learning or quick demos.
Always authenticate users from a database in real applications.
Mistake 2: Returning the Entire User Entity
After authentication, return only the information the client needs, such as the JWT and its expiration time.
Avoid returning sensitive fields like the password.
Mistake 3: Using Plain-Text Passwords in Production
Comparing plain-text passwords is insecure.
Always store hashed passwords instead of the original password.
Mistake 4: Querying More Data Than Necessary
During login, retrieve only the user record required for authentication.
Avoid loading unnecessary related data that isn't needed to generate the JWT.
Key Takeaways
Hardcoded credentials are useful for learning but not for real applications.
Entity Framework Core allows user authentication using data stored in SQL Server.
The JWT generation process remains unchanged after moving to a database.
ApplicationDbContext provides access to the Users table through Entity Framework Core.
If a matching user is found, the API generates a JWT; otherwise, it returns 401 Unauthorized.
Storing plain-text passwords is still insecure and should be replaced with password hashing.
Our SecureShop API now authenticates users from a real SQL Server database instead of hardcoded values. In the next article, we'll improve security by implementing password hashing and verification, ensuring that user passwords are never stored or compared in plain text.