UDT-002: Logout + Refresh Token con rotación y chain revocation #3
@@ -122,6 +122,65 @@ public class JwtServiceTests : IDisposable
|
||||
Assert.Equal("2", parsed2.Subject);
|
||||
}
|
||||
|
||||
// T-040: GetPrincipalFromExpiredToken
|
||||
|
||||
[Fact]
|
||||
public void GetPrincipalFromExpiredToken_ValidSignatureExpired_ReturnsPrincipal()
|
||||
{
|
||||
// Generate a token that will expire in 1 second, then manually create an expired JWT
|
||||
// using JwtSecurityTokenHandler directly (bypassing the service to control timestamps).
|
||||
var signingKey = new RsaSecurityKey(_rsa);
|
||||
var credentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256);
|
||||
|
||||
var past = DateTime.UtcNow.AddHours(-2);
|
||||
var descriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new System.Security.Claims.ClaimsIdentity(
|
||||
[new System.Security.Claims.Claim("sub", "1")]),
|
||||
Issuer = "sigcm2.api",
|
||||
Audience = "sigcm2.web",
|
||||
IssuedAt = past,
|
||||
NotBefore = past,
|
||||
Expires = past.AddMinutes(60), // expired 1h ago
|
||||
SigningCredentials = credentials,
|
||||
};
|
||||
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var token = handler.CreateToken(descriptor);
|
||||
var expiredToken = handler.WriteToken(token);
|
||||
|
||||
// Now use the service — it must validate the signature but ignore expiry
|
||||
var principal = _jwtService.GetPrincipalFromExpiredToken(expiredToken);
|
||||
|
||||
Assert.NotNull(principal);
|
||||
// The JWT handler maps "sub" to ClaimTypes.NameIdentifier by default,
|
||||
// but our JwtService uses a custom "sub" claim. Check both.
|
||||
var sub = principal.FindFirst("sub")?.Value
|
||||
?? principal.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
Assert.Equal("1", sub);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrincipalFromExpiredToken_InvalidSignature_Throws()
|
||||
{
|
||||
// Sign with a different RSA key
|
||||
using var otherRsa = System.Security.Cryptography.RSA.Create(2048);
|
||||
var otherOptions = new JwtOptions { Issuer = "sigcm2.api", Audience = "sigcm2.web", AccessTokenMinutes = 60 };
|
||||
var otherService = new JwtService(otherRsa, otherOptions);
|
||||
var tokenFromOtherKey = otherService.GenerateAccessToken(MakeUsuario());
|
||||
|
||||
// Validating with the correct key should throw
|
||||
Assert.Throws<Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException>(
|
||||
() => _jwtService.GetPrincipalFromExpiredToken(tokenFromOtherKey));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPrincipalFromExpiredToken_MalformedToken_Throws()
|
||||
{
|
||||
Assert.ThrowsAny<Exception>(
|
||||
() => _jwtService.GetPrincipalFromExpiredToken("not.a.valid.jwt"));
|
||||
}
|
||||
|
||||
private static Usuario MakeUsuario(int id = 1, string username = "admin")
|
||||
=> new(id, username, "$2a$12$hash", "Administrador", "Sistema", null, "admin", "[\"*\"]", true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user