DeutschLernen/GermanApp/Infrastructure/Services/AuthService.cs
Lasse Rune Hansen d508d498c0 feat(backend/auth): implement User Authentication feature
- Add DTOs: RegisterDto, LoginDto, AuthResponse
- Add IAuthService interface
- Implement AuthService with JWT token generation
- Create AuthController with register, login, me endpoints
- Add JWT configuration to appsettings.json
- Configure JWT Bearer authentication in Program.cs
- Add PasswordHasher for custom User entity
- Update User entity with ChangePassword method
- Add necessary NuGet packages for JWT auth

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-05 13:27:43 +02:00

132 lines
4.4 KiB
C#

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using GermanApp.Application.DTOs.Auth;
using GermanApp.Application.Interfaces;
using GermanApp.Domain.Entities;
using GermanApp.Infrastructure.Data.DbContext;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
namespace GermanApp.Infrastructure.Services;
/// <summary>
/// Authentication service implementation.
/// Part of the Infrastructure layer.
/// </summary>
public class AuthService : IAuthService
{
private readonly AppDbContext _dbContext;
private readonly IPasswordHasher<User> _passwordHasher;
private readonly IConfiguration _configuration;
public AuthService(
AppDbContext dbContext,
IPasswordHasher<User> passwordHasher,
IConfiguration configuration)
{
_dbContext = dbContext;
_passwordHasher = passwordHasher;
_configuration = configuration;
}
/// <summary>
/// Registers a new user.
/// </summary>
public async Task<AuthResponse> RegisterAsync(RegisterDto registerDto)
{
// Check if username or email already exists
if (await _dbContext.Users.AnyAsync(u => u.Username == registerDto.Username))
throw new InvalidOperationException("Username already taken");
if (await _dbContext.Users.AnyAsync(u => u.Email == registerDto.Email))
throw new InvalidOperationException("Email already in use");
// Hash password and create user
var user = User.Create(registerDto.Username, registerDto.Email.ToLowerInvariant(), string.Empty);
var passwordHash = _passwordHasher.HashPassword(user, registerDto.Password);
user.ChangePassword(passwordHash);
_dbContext.Users.Add(user);
await _dbContext.SaveChangesAsync();
// Generate JWT token
var token = GenerateJwtToken(user);
return new AuthResponse
{
UserId = user.Id,
Username = user.Username,
Email = user.Email,
Token = token,
ExpiresAt = DateTime.UtcNow.AddHours(24)
};
}
/// <summary>
/// Authenticates a user and returns a JWT token.
/// </summary>
public async Task<AuthResponse> LoginAsync(LoginDto loginDto)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Email == loginDto.Email);
if (user == null)
throw new UnauthorizedAccessException("Invalid email or password");
// Verify password
var result = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, loginDto.Password);
if (result == PasswordVerificationResult.Failed)
throw new UnauthorizedAccessException("Invalid email or password");
// Generate JWT token
var token = GenerateJwtToken(user);
return new AuthResponse
{
UserId = user.Id,
Username = user.Username,
Email = user.Email,
Token = token,
ExpiresAt = DateTime.UtcNow.AddHours(24)
};
}
/// <summary>
/// Gets the current authenticated user.
/// </summary>
public async Task<User?> GetCurrentUserAsync(int userId)
{
return await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == userId);
}
/// <summary>
/// Generates a JWT token for the given user.
/// </summary>
private string GenerateJwtToken(User user)
{
var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? "super-secret-key-at-least-32-characters"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Role, "User")
};
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"] ?? "DeutschLernen",
audience: _configuration["Jwt:Audience"] ?? "DeutschLernen",
claims: claims,
expires: DateTime.UtcNow.AddHours(24),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}