- Project Setup
- Running the Application
- Entity Framework Migrations
- Connecting ASP.NET to MySQL and Setting Up Basic API
- Handling API Requests
- Password Hashing Implementation
- JWT with HttpOnly Cookie-Based Authentication
- How to Setup CORS Configuration for Cross-Origin Requests
- Debugging Tips
- Conclusion
- Notes
- Running & Testing
dotnet new webapi # Create a new ASP.NET Web API projectdotnet watch run # Run the application with hot reloaddotnet ef migrations add init # Initialize the migration (set up models & DBContext first)- Configure
appsettings.jsonconnection string:
"DefaultConnection": "Server=localhost;Port=3306;Database=DatabaseTest;User Id=root;Password=Pogipogi96;"dotnet ef migrations remove # Undo or remove migrations
dotnet ef database update # Update the database with the latest migrations- Install Dependencies
- Entity Framework Core
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.Tools
- Pomelo.EntityFrameworkCore.MySql
- Set Up Your Models
- Configure ApplicationDBContext:
- Add your model to
ApplicationDbContext.cs - Edit the connection string in
appsettings.json
- Add your model to
- Initialize DbContext in
Program.cs:- Register
DbContextin builder services
- Register
- Run Migrations and Database Update:
- Check if the database initializes successfully
- Set Up Controllers and Routes
- Configure Middleware in
Program.cs:builder.Services.AddControllers(); app.MapControllers();
- Run and Test the API
- Create DTO Class:
- Define properties for input validation (e.g.,
Required,MaxLengthattributes)
- Define properties for input validation (e.g.,
- Create Mapper Static Class:
public static Todo TodoRequestToTodo(this CreateTodoRequestDto TodoDto) { return new Todo { Title = TodoDto.Title, Description = TodoDto.Description, IsComplete = TodoDto.IsComplete, CreatedOn = TodoDto.CreatedOn, UpdatedOn = TodoDto.UpdatedOn }; }
- Implement HTTP POST Controller Method
- Create DTO Schema:
- Define properties for the values you want to update
- Create Mapper Method:
- Map the updated values accordingly
- Set Up Route Method:
- Use
[HttpPatch]or[HttpPut]withFromRoute(ID) andFromBody(DTO)
- Use
- Find Data and Update:
- Validate if the record exists
- Update using
DbContext - Save changes
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="9.0.2" />using Microsoft.AspNetCore.Cryptography.KeyDerivation;
var hashedPassword = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: user.password,
salt: System.Text.Encoding.UTF8.GetBytes("salt"),
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8
));[HttpPost]
public IActionResult CreateUser([FromBody] CreateUserDto userModel) {
if (!ModelState.IsValid)
return BadRequest(ModelState);
var user = userModel.CreateUserRequest();
user.password = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: user.password,
salt: System.Text.Encoding.UTF8.GetBytes("salt"),
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8
));
_context.Users.Add(user);
_context.SaveChanges();
return Ok(user);
}string hashedPassword = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: userModel.password,
salt: Encoding.UTF8.GetBytes("salt"),
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8
));[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginUserDto userModel)
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.username == userModel.username);
if (user == null)
return Unauthorized(new { message = "User not found" });
if (string.IsNullOrWhiteSpace(userModel.username) || string.IsNullOrWhiteSpace(userModel.password))
return Unauthorized(new { message = "Invalid username or password" });
string hashedPassword = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: userModel.password,
salt: Encoding.UTF8.GetBytes("salt"),
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8
));
if (hashedPassword != user.password)
return Unauthorized(new { message = "Invalid username or password" });
var token = _jwtService.GenerateJwtToken(user.username);
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = false,
SameSite = SameSiteMode.Strict,
Expires = DateTime.UtcNow.AddMinutes(30)
};
Response.Cookies.Append("jwt", token, cookieOptions);
return Ok(new { token, message = "Logged in successfully" });
}This guide explains how to implement JWT-based authentication using HttpOnly cookies in an ASP.NET Core Web API application. This ensures secure authentication while preventing XSS attacks.
- Create a user model with hashed passwords.
- Define routes for user creation, authentication, and deletion.
Ensure the following dependencies are installed:
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />Create a service for generating JWT tokens:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace Web_API.Services
{
public class JWTService
{
private readonly string _secret;
public JWTService(string secret)
{
_secret = secret;
}
public string GenerateJwtToken(string username)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username) }),
Expires = DateTime.UtcNow.AddMinutes(30),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
}Modify the login controller to generate and store the JWT in an HttpOnly cookie (make sure the login login authentication is correct before generating token):
var token = _jwtService.GenerateJwtToken(user.username);
var cookieOptions = new CookieOptions
{
HttpOnly = true, // Prevents JavaScript access (XSS protection)
Secure = false, // ❌ Set to true in production
SameSite = SameSiteMode.Strict, // Prevents CSRF attacks
Expires = DateTime.UtcNow.AddMinutes(30)
};
Response.Cookies.Append("jwt", token, cookieOptions); // store the jwt in cookie https only storageConfigure authentication services in Program.cs:
var key = Encoding.UTF8.GetBytes("this is my custom Secret key for authentication");
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true
};
});Enable CORS:
builder.Services.AddCors(options =>
{
options.AddPolicy("MyAllowSpecificOrigins",
policy =>
{
policy.WithOrigins("http://localhost:3000") // client http
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials(); // enable credentials for authentications
});
});// Define the endpoints to allow in CORS.
app.MapGet("/profile", async context => await context.Response.WriteAsync("profile"))
.RequireCors(MyAllowSpecificOrigins);
app.MapGet("/home", async context => await context.Response.WriteAsync("home"))
.RequireCors(MyAllowSpecificOrigins);Enable authentication and authorization middleware:
app.UseAuthentication();
app.UseAuthorization();[HttpGet("validate")]
[EnableCors("_myAllowSpecificOrigins")]
public IActionResult ValidateJWT()
{
var token = Request.Cookies["jwt"];
if (string.IsNullOrEmpty(token))
{
return Unauthorized();
}
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);
var username = jwtToken.Claims.First(claim => claim.Type == "unique_name").Value;
return Ok(new { username });
}[HttpPost("logout")]
public IActionResult Logout()
{
Response.Cookies.Delete("jwt");
return Ok(new { message = "Logged out successfully" });
}- Verify if the login route correctly generates a JWT token.
- Check if the JWT token is stored correctly in the client's cookies.
- Ensure authentication logic correctly validates user credentials.
- Debug CORS issues by ensuring the frontend correctly sends requests with credentials.
Following this guide, you will successfully implement JWT-based authentication using HttpOnly cookies in an ASP.NET Core Web API, ensuring secure and seamless user authentication.
Register Cors Builder with basic Configuration:
builder.Services.AddCors(options =>
{
options.AddPolicy("MyAllowSpecificOrigins",
policy =>
{
policy.WithOrigins("http://localhost:3000") // client http
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials(); // enable credentials for authentications
});
});Enabling Endpoints of clients routes:
// Define the endpoints to allow in CORS.
app.MapGet("/profile", async context => await context.Response.WriteAsync("profile"))
.RequireCors(MyAllowSpecificOrigins);
app.MapGet("/home", async context => await context.Response.WriteAsync("home"))
.RequireCors(MyAllowSpecificOrigins);- Always ensure the database connection is correctly configured.
- Run
dotnet ef database updateafter making model changes. - Ensure your DTOs handle required validation and security properly.
- Use dependency injection for
DbContextto manage database interactions efficiently.
- Run
dotnet watch runto start the API - Use Postman or any API testing tool to test endpoints
- Verify database changes using MySQL client tools