Add JWT authentication with AuthController and services

This commit is contained in:
Denis Urs Rudolph
2026-04-03 21:00:16 +02:00
parent 8bfd49e0ab
commit b6962e1024
6 changed files with 224 additions and 12 deletions
+92
View File
@@ -0,0 +1,92 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RacePlannerApi.Data;
using RacePlannerApi.DTOs;
using RacePlannerApi.Models;
using RacePlannerApi.Services;
namespace RacePlannerApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly RacePlannerDbContext _context;
private readonly JwtTokenService _jwtService;
public AuthController(RacePlannerDbContext context, JwtTokenService jwtService)
{
_context = context;
_jwtService = jwtService;
}
[HttpPost("register")]
public async Task<ActionResult<AuthResponse>> Register(RegisterRequest request)
{
// Check if email already exists
if (await _context.Users.AnyAsync(u => u.Email == request.Email))
{
return Conflict(new { error = "Email already registered" });
}
// Create new user
var user = new User
{
Email = request.Email,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password),
Name = request.Name,
Role = request.Role
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
// Generate token
var token = _jwtService.GenerateToken(user);
return Ok(new AuthResponse
{
Token = token,
User = new UserDto
{
Id = user.Id,
Email = user.Email,
Name = user.Name,
Role = user.Role.ToString()
}
});
}
[HttpPost("login")]
public async Task<ActionResult<AuthResponse>> Login(LoginRequest request)
{
// Find user by email
var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == request.Email);
if (user == null)
{
return Unauthorized(new { error = "Invalid credentials" });
}
// Verify password
if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
return Unauthorized(new { error = "Invalid credentials" });
}
// Generate token
var token = _jwtService.GenerateToken(user);
return Ok(new AuthResponse
{
Token = token,
User = new UserDto
{
Id = user.Id,
Email = user.Email,
Name = user.Name,
Role = user.Role.ToString()
}
});
}
}
+45
View File
@@ -0,0 +1,45 @@
using System.ComponentModel.DataAnnotations;
using RacePlannerApi.Models;
namespace RacePlannerApi.DTOs;
public class RegisterRequest
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
[MinLength(8)]
public string Password { get; set; } = string.Empty;
[Required]
[MinLength(2)]
public string Name { get; set; } = string.Empty;
public UserRole Role { get; set; } = UserRole.Participant;
}
public class LoginRequest
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
public string Password { get; set; } = string.Empty;
}
public class AuthResponse
{
public string Token { get; set; } = string.Empty;
public UserDto User { get; set; } = null!;
}
public class UserDto
{
public Guid Id { get; set; }
public string Email { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
}
+29
View File
@@ -1,5 +1,9 @@
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using RacePlannerApi.Data;
using RacePlannerApi.Services;
var builder = WebApplication.CreateBuilder(args);
@@ -8,6 +12,29 @@ builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi();
// Configure JWT Authentication
var jwtKey = builder.Configuration["Jwt:Key"] ?? "your-secret-key-here-minimum-32-characters-long";
var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "RacePlannerApi";
var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "RacePlannerClient";
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtIssuer,
ValidAudience = jwtAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
};
});
// Register services
builder.Services.AddScoped<JwtTokenService>();
// Configure Entity Framework Core with PostgreSQL
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? "Host=localhost;Database=RacePlanner;Username=postgres;Password=postgres";
@@ -40,6 +67,8 @@ app.UseHttpsRedirection();
// Apply CORS
app.UseCors("AllowFrontend");
// Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
+3
View File
@@ -7,12 +7,15 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.5" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
</ItemGroup>
</Project>
+43
View File
@@ -0,0 +1,43 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using RacePlannerApi.Models;
namespace RacePlannerApi.Services;
public class JwtTokenService
{
private readonly IConfiguration _configuration;
public JwtTokenService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateToken(User user)
{
var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? "your-secret-key-here-minimum-32-characters-long"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Name, user.Name),
new Claim("role", user.Role.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"] ?? "RacePlannerApi",
audience: _configuration["Jwt:Audience"] ?? "RacePlannerClient",
claims: claims,
expires: DateTime.Now.AddHours(24),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}