Auth : rejeter les jetons dont le compte n'existe plus (validation de session)
Corrige un contournement : après réinitialisation de la base (même JWT_SECRET_KEY), un jeton restait cryptographiquement valide et donnait accès alors que le compte n'existait plus, permettant d'atteindre l'onboarding sans s'inscrire. - Backend : JwtBearer.OnTokenValidated vérifie désormais l'existence du compte référencé par le claim sub (lu sous 'sub' ou NameIdentifier selon le remapping) et rejette le jeton (401) si le compte est introuvable. - Backend : nouvel endpoint protégé GET /api/auth/moi (SessionController) renvoyant 200 si la session est réellement valide, 401 sinon. - Frontend : authGuard valide la session côté serveur (GET /auth/moi) au lieu de se fier au seul jeton local ; sur échec, redirection vers /login (jeton purgé par l'intercepteur sur 401). AuthService.validerSession() ajouté. Tests backend : 54/54 au vert.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SchotzIadKmTracker.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint de validation de session. Ce controller est soumis à l'autorisation globale
|
||||
/// (RequireAuthenticatedUser) : il n'est atteignable qu'avec un jeton valide ET un compte
|
||||
/// existant (le middleware JWT rejette un jeton dont le compte est introuvable via
|
||||
/// OnTokenValidated). Le frontend l'appelle pour confirmer qu'une session est réellement
|
||||
/// valide au chargement des routes protégées.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
public class SessionController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Retourne 200 si la session est valide (jeton valide + compte existant), 401 sinon.
|
||||
/// </summary>
|
||||
[HttpGet("moi")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public IActionResult Moi()
|
||||
{
|
||||
return Ok(new { authentifie = true });
|
||||
}
|
||||
}
|
||||
@@ -137,6 +137,37 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecretKey)),
|
||||
ClockSkew = TimeSpan.FromSeconds(30) // tolérance d'horloge réduite
|
||||
};
|
||||
|
||||
// Un jeton peut être cryptographiquement valide (bonne signature, non expiré) alors
|
||||
// que le compte n'existe plus (ex. base réinitialisée avec la même clé JWT). On vérifie
|
||||
// donc l'EXISTENCE du compte référencé par le claim `sub` à chaque requête : sinon le
|
||||
// jeton est rejeté (401), ce qui force la reconnexion / l'inscription côté frontend.
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnTokenValidated = async context =>
|
||||
{
|
||||
// ASP.NET Core remappe par défaut le claim « sub » vers ClaimTypes.NameIdentifier.
|
||||
// On cherche donc l'identifiant sous les deux noms possibles.
|
||||
var principal = context.Principal;
|
||||
var sub = principal?.FindFirst(System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames.Sub)?.Value
|
||||
?? principal?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
if (!int.TryParse(sub, out var compteId))
|
||||
{
|
||||
context.Fail("Jeton invalide : identifiant de compte manquant.");
|
||||
return;
|
||||
}
|
||||
|
||||
var db = context.HttpContext.RequestServices.GetRequiredService<AppDbContext>();
|
||||
var compteExiste = await db.Comptes.AsNoTracking()
|
||||
.AnyAsync(c => c.Id == compteId, context.HttpContext.RequestAborted);
|
||||
|
||||
if (!compteExiste)
|
||||
{
|
||||
context.Fail("Compte introuvable : jeton non valide.");
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<SchotzIadKmTracker.Api.Repositories.IVehiculeRepository,
|
||||
|
||||
Reference in New Issue
Block a user