using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; using w4c_workflows.Services; using Xunit; namespace w4c_workflows.Tests; /// /// P1-14: the shared symmetric key alone is weak, so issuer/audience must be /// enforced whenever the deployment declares them, and left optional otherwise /// (w4c-auth may not stamp them). A token signed with the wrong key, from the /// wrong issuer or for the wrong audience must never validate. /// public class JwtValidatorTests { private const string SigningKey = "unit-test-signing-key-that-is-long-enough-1234567890"; private const string OtherKey = "another-signing-key-that-is-long-enough-0987654321"; private static string Token( string key, string? issuer = "w4c-auth", string? audience = "w4c", DateTime? expires = null, DateTime? notBefore = null) { var credentials = new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)), SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: issuer, audience: audience, claims: new[] { new Claim("tenant_id", "tenant-42"), new Claim("sub", "user-1") }, notBefore: notBefore ?? DateTime.UtcNow.AddMinutes(-1), expires: expires ?? DateTime.UtcNow.AddMinutes(5), signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token); } [Fact] public void Valid_token_is_accepted_and_extracts_the_tenant() { var principal = JwtValidator.Validate($"Bearer {Token(SigningKey)}", SigningKey, "w4c-auth", "w4c"); Assert.NotNull(principal); Assert.Equal("tenant-42", principal!.FindFirst("tenant_id")?.Value); } [Fact] public void Issuer_and_audience_are_enforced_when_configured() { var bearer = $"Bearer {Token(SigningKey, issuer: "attacker", audience: "w4c")}"; Assert.Null(JwtValidator.Validate(bearer, SigningKey, "w4c-auth", "w4c")); bearer = $"Bearer {Token(SigningKey, issuer: "w4c-auth", audience: "someone-else")}"; Assert.Null(JwtValidator.Validate(bearer, SigningKey, "w4c-auth", "w4c")); } [Fact] public void Issuer_and_audience_are_optional_when_the_deployment_does_not_declare_them() { // A token that carries an issuer/audience still validates when the server // is not configured to check them (backward compatibility with w4c-auth). var principal = JwtValidator.Validate($"Bearer {Token(SigningKey)}", SigningKey); Assert.NotNull(principal); } [Fact] public void Token_signed_with_another_key_is_rejected() { Assert.Null(JwtValidator.Validate($"Bearer {Token(OtherKey)}", SigningKey, "w4c-auth", "w4c")); } [Fact] public void Expired_token_is_rejected() { var expired = Token( SigningKey, notBefore: DateTime.UtcNow.AddHours(-1), expires: DateTime.UtcNow.AddMinutes(-5)); Assert.Null(JwtValidator.Validate($"Bearer {expired}", SigningKey, "w4c-auth", "w4c")); } [Theory] [InlineData("")] [InlineData("not-a-bearer")] [InlineData("Bearer ")] public void Missing_or_malformed_header_is_rejected(string header) { Assert.Null(JwtValidator.Validate(header, SigningKey, "w4c-auth", "w4c")); } }