DeutschLernen/GermanApp/Presentation/Controllers/AuthController.cs
Lasse Rune Hansen ef72cbea18 fix(backend/auth): Fix JWT token claim mapping for user ID
- Changed AuthService.GenerateJwtToken to use JWT standard claims:
  - JwtRegisteredClaimNames.Sub for user ID (instead of ClaimTypes.NameIdentifier)
  - JwtRegisteredClaimNames.Name for username
  - JwtRegisteredClaimNames.Email for email
  - JwtRegisteredClaimNames.UniqueName for additional username claim
  - Kept ClaimTypes.Role for role

- Updated AuthController.GetCurrentUser to use JwtRegisteredClaimNames.Sub
- Updated AdminController.DeleteUserAsync to use JwtRegisteredClaimNames.Sub

This fixes the 401 error when calling /me after login. The issue was that
tokens were being generated with ClaimTypes.NameIdentifier claim, but the
JWT middleware doesn't automatically map this to a claim that can be found
with User.FindFirst(). Using standard JWT claims (sub, name, email) ensures
proper compatibility with ASP.NET Core's JWT authentication.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-14 17:38:27 +02:00

164 lines
5.3 KiB
C#

using GermanApp.Application.DTOs.Auth;
using GermanApp.Application.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Security.Claims;
namespace GermanApp.Presentation.Controllers;
/// <summary>
/// Controller for authentication endpoints.
/// Part of the Presentation layer.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
public AuthController(IAuthService authService)
{
_authService = authService;
}
/// <summary>
/// Register a new user.
/// </summary>
/// <param name="registerDto">Registration data</param>
/// <returns>Authentication response with JWT token</returns>
[HttpPost("register")]
[AllowAnonymous]
[ProducesResponseType(typeof(AuthResponse), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Register([FromBody] RegisterDto registerDto)
{
try
{
var result = await _authService.RegisterAsync(registerDto);
return Ok(result);
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
}
}
/// <summary>
/// Login an existing user.
/// </summary>
/// <param name="loginDto">Login data</param>
/// <returns>Authentication response with JWT token</returns>
[HttpPost("login")]
[AllowAnonymous]
[ProducesResponseType(typeof(AuthResponse), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)]
public async Task<IActionResult> Login([FromBody] LoginDto loginDto)
{
try
{
var result = await _authService.LoginAsync(loginDto);
return Ok(result);
}
catch (UnauthorizedAccessException ex)
{
return Unauthorized(ex.Message);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
}
}
/// <summary>
/// Get current authenticated user information.
/// </summary>
/// <returns>Current user information</returns>
[HttpGet("me")]
[Authorize]
[ProducesResponseType(typeof(AuthResponse), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.Unauthorized)]
public async Task<IActionResult> GetCurrentUser()
{
try
{
// Get user ID from JWT "sub" claim (standard JWT claim for subject/user ID)
var userIdClaim = User.FindFirst(JwtRegisteredClaimNames.Sub) ?? User.FindFirst("sub");
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId) || userId == 0)
return Unauthorized();
var user = await _authService.GetCurrentUserAsync(userId);
if (user == null)
return Unauthorized();
return Ok(new AuthResponse
{
UserId = user.Id,
Username = user.Username,
Email = user.Email
});
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
}
}
/// <summary>
/// Refresh the access token using a refresh token.
/// </summary>
/// <param name="refreshToken">The refresh token</param>
/// <returns>New access token and refresh token</returns>
[HttpPost("refresh")]
[ProducesResponseType(typeof(RefreshTokenResponse), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Refresh([FromBody] string refreshToken)
{
try
{
var result = await _authService.RefreshTokenAsync(refreshToken);
return Ok(result);
}
catch (UnauthorizedAccessException ex)
{
return Unauthorized(ex.Message);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
}
}
/// <summary>
/// Revoke a refresh token.
/// </summary>
/// <param name="refreshToken">The refresh token to revoke</param>
/// <returns>Success or error response</returns>
[HttpPost("revoke-refresh")]
[Authorize]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> RevokeRefreshToken([FromBody] string refreshToken)
{
try
{
await _authService.RevokeRefreshTokenAsync(refreshToken);
return Ok(new { message = "Refresh token revoked successfully" });
}
catch (UnauthorizedAccessException ex)
{
return Unauthorized(ex.Message);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
}
}
}