diff --git a/src/Paseto/Handlers/PasetoPurposeHandler.cs b/src/Paseto/Handlers/PasetoPurposeHandler.cs
index 7da50a2..8b22245 100644
--- a/src/Paseto/Handlers/PasetoPurposeHandler.cs
+++ b/src/Paseto/Handlers/PasetoPurposeHandler.cs
@@ -46,6 +46,7 @@ public virtual PasetoTokenValidationResult ValidateTokenPayload(PasetoToken toke
ValidateLifetime(token, validationParameters);
ValidateAudience(token, validationParameters);
ValidateIssuer(token, validationParameters);
+ ValidateSubject(token, validationParameters);
}
catch (Exception ex)
{
@@ -87,4 +88,13 @@ protected virtual void ValidateIssuer(PasetoToken token, PasetoTokenValidationPa
if (token.Payload.HasIssuer())
new EqualValidator(token.Payload, PasetoRegisteredClaimNames.Issuer).Validate(validationParameters.ValidIssuer);
}
+
+ protected virtual void ValidateSubject(PasetoToken token, PasetoTokenValidationParameters validationParameters)
+ {
+ if (!validationParameters.ValidateSubject && !string.IsNullOrWhiteSpace(validationParameters.ValidSubject))
+ return;
+
+ if (token.Payload.HasSubject())
+ new EqualValidator(token.Payload, PasetoRegisteredClaimNames.Subject).Validate(validationParameters.ValidSubject);
+ }
}
diff --git a/src/Paseto/PasetoTokenValidationParameters.cs b/src/Paseto/PasetoTokenValidationParameters.cs
index 294b3ca..77749b5 100644
--- a/src/Paseto/PasetoTokenValidationParameters.cs
+++ b/src/Paseto/PasetoTokenValidationParameters.cs
@@ -22,6 +22,11 @@ public class PasetoTokenValidationParameters
///
public bool ValidateIssuer { get; set; }
+ ///
+ /// Gets or sets a value for comparing the subject of the payload.
+ ///
+ public bool ValidateSubject { get; set; }
+
///
/// Gets or sets the valid audience for comparing against the payload-provided aud.
///
@@ -31,4 +36,9 @@ public class PasetoTokenValidationParameters
/// Gets or sets the valid issuer for comparing against the payload-provided iss.
///
public string ValidIssuer { get; set; }
+
+ ///
+ /// Gets or sets the valid subject for comparing against the payload-provided sub.
+ ///
+ public string ValidSubject { get; set; }
}
\ No newline at end of file
diff --git a/tests/Paseto.Tests/PasetoValidationTest.cs b/tests/Paseto.Tests/PasetoValidationTest.cs
index d6472d2..bf5ecce 100644
--- a/tests/Paseto.Tests/PasetoValidationTest.cs
+++ b/tests/Paseto.Tests/PasetoValidationTest.cs
@@ -1,4 +1,5 @@
-using System.ComponentModel;
+using System;
+using System.ComponentModel;
using System.Linq;
using FluentAssertions;
using Paseto.Builder;
@@ -22,7 +23,7 @@ public void TokenWithValidIssuerValidationSucceeds(ProtocolVersion version, Purp
ValidIssuer = "valid-issuer",
};
- var (token, decodeKey) = GenerateToken(version, purpose, "valid-issuer");
+ var (token, decodeKey) = GenerateToken(version, purpose, PasetoRegisteredClaimNames.Issuer, "valid-issuer");
var decoded = new PasetoBuilder()
.Use(version, purpose)
.WithKey(decodeKey)
@@ -44,7 +45,7 @@ public void TokenWithInValidIssuerValidationFails(ProtocolVersion version, Purpo
ValidIssuer = "valid-issuer",
};
- var (token, decodeKey) = GenerateToken(version, purpose, "invalid-issuer");
+ var (token, decodeKey) = GenerateToken(version, purpose, PasetoRegisteredClaimNames.Issuer, "invalid-issuer");
var decoded = new PasetoBuilder()
.Use(version, purpose)
.WithKey(decodeKey)
@@ -53,9 +54,64 @@ public void TokenWithInValidIssuerValidationFails(ProtocolVersion version, Purpo
decoded.IsValid.Should().BeFalse();
}
- private static (string token, PasetoKey decodeKey) GenerateToken(ProtocolVersion version, Purpose purpose, string issuer)
+ [Theory(DisplayName = "Should succeed on token with valid subject")]
+ [InlineData(ProtocolVersion.V3, Purpose.Local)]
+ [InlineData(ProtocolVersion.V3, Purpose.Public)]
+ [InlineData(ProtocolVersion.V4, Purpose.Local)]
+ [InlineData(ProtocolVersion.V4, Purpose.Public)]
+ public void TokenWithValidSubjectValidationSucceeds(ProtocolVersion version, Purpose purpose)
+ {
+ var validationParameters = new PasetoTokenValidationParameters()
+ {
+ ValidateSubject = true,
+ ValidSubject = "valid-subject",
+ };
+
+ var (token, decodeKey) = GenerateToken(version, purpose, PasetoRegisteredClaimNames.Subject, "valid-subject");
+ var decoded = new PasetoBuilder()
+ .Use(version, purpose)
+ .WithKey(decodeKey)
+ .Decode(token, validationParameters);
+
+ decoded.IsValid.Should().BeTrue();
+ }
+
+ [Theory(DisplayName = "Should fail on token with invalid subject")]
+ [InlineData(ProtocolVersion.V3, Purpose.Local)]
+ [InlineData(ProtocolVersion.V3, Purpose.Public)]
+ [InlineData(ProtocolVersion.V4, Purpose.Local)]
+ [InlineData(ProtocolVersion.V4, Purpose.Public)]
+ public void TokenWithInValidSubjectValidationFails(ProtocolVersion version, Purpose purpose)
+ {
+ var validationParameters = new PasetoTokenValidationParameters()
+ {
+ ValidateSubject = true,
+ ValidSubject = "valid-subject",
+ };
+
+ var (token, decodeKey) = GenerateToken(version, purpose, PasetoRegisteredClaimNames.Subject, "invalid-subject");
+ var decoded = new PasetoBuilder()
+ .Use(version, purpose)
+ .WithKey(decodeKey)
+ .Decode(token, validationParameters);
+
+ decoded.IsValid.Should().BeFalse();
+ }
+
+ private static (string token, PasetoKey decodeKey) GenerateToken(ProtocolVersion version, Purpose purpose, string claimName, string claimValue)
{
var builder = new PasetoBuilder().Use(version, purpose);
+ switch (claimName)
+ {
+ case PasetoRegisteredClaimNames.Issuer:
+ builder.Issuer(claimValue);
+ break;
+ case PasetoRegisteredClaimNames.Subject:
+ builder.Subject(claimValue);
+ break;
+ default:
+ throw new NotImplementedException();
+ }
switch (purpose)
{
case Purpose.Local:
@@ -63,7 +119,6 @@ private static (string token, PasetoKey decodeKey) GenerateToken(ProtocolVersion
var key = builder.GenerateSymmetricKey();
var token = builder
.WithKey(key)
- .Issuer(issuer)
.Encode();
return (token, key);
}
@@ -72,7 +127,6 @@ private static (string token, PasetoKey decodeKey) GenerateToken(ProtocolVersion
var keyPair = builder.GenerateAsymmetricKeyPair(Enumerable.Repeat((byte)0x00, 32).ToArray());
var token = builder
.WithKey(keyPair.SecretKey)
- .Issuer(issuer)
.Encode();
return (token, keyPair.PublicKey);
}