Skip to content

Commit

Permalink
Add support for nbf (Not Before) token claim validation in V2 (#81)
Browse files Browse the repository at this point in the history
* Adding the support of nbn claim
* Bumping minor version
  • Loading branch information
abatishchev authored Apr 8, 2017
1 parent 448f085 commit ac3568a
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 28 deletions.
2 changes: 1 addition & 1 deletion JWT.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package>
<metadata>
<id>JWT</id>
<version>2.2.0-beta</version>
<version>2.2.1-beta</version>
<authors>John Sheehan, Michael Lehenbauer, Alexander Batishchev</authors>
<owners>johnsheehan, devinrader, abatishchev</owners>
<description>Jwt.Net, a JWT (JSON Web Token) implementation for .NET</description>
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ As described in the [JWT RFC](https://tools.ietf.org/html/draft-ietf-oauth-json-
IDateTimeProvider provider = new UtcDateTimeProvider();
var now = provider.GetNow();

var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); // or use JwtValidator.UnixEpoch
var secondsSinceEpoch = Math.Round((now - unixEpoch).TotalSeconds);

var payload = new Dictionary<string, object>
Expand Down
61 changes: 41 additions & 20 deletions src/JWT/JwtValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace JWT
{
public sealed class JwtValidator : IJwtValidator
{
private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
public static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

private readonly IJsonSerializer _jsonSerializer;
private readonly IDateTimeProvider _dateTimeProvider;
Expand All @@ -28,32 +28,53 @@ public void Validate(string payloadJson, string decodedCrypto, string decodedSig
};
}

// verify exp claim https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.4
var payloadData = _jsonSerializer.Deserialize<Dictionary<string, object>>(payloadJson);

var now = _dateTimeProvider.GetNow();
var secondsSinceEpoch = Math.Round((now - UnixEpoch).TotalSeconds);

// verify exp claim https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.4
object expObj;
if (!payloadData.TryGetValue("exp", out expObj) || expObj == null)
{
return;
}
int expInt;
try
{
expInt = Convert.ToInt32(expObj);
}
catch (FormatException)
if (payloadData.TryGetValue("exp", out expObj))
{
throw new SignatureVerificationException("Claim 'exp' must be an integer.");
int expInt;
try
{
expInt = Convert.ToInt32(expObj);
}
catch
{
throw new SignatureVerificationException("Claim 'exp' must be an integer.");
}

if (secondsSinceEpoch >= expInt)
{
throw new TokenExpiredException("Token has expired.")
{
Expiration = UnixEpoch.AddSeconds(expInt),
PayloadData = payloadData
};
}
}

var now = _dateTimeProvider.GetNow();
var secondsSinceEpoch = Math.Round((now - _unixEpoch).TotalSeconds);
if (secondsSinceEpoch >= expInt)
// verify nbf claim https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.5
object nbfObj;
if (payloadData.TryGetValue("nbf", out nbfObj))
{
throw new TokenExpiredException("Token has expired.")
int nbfInt;
try
{
Expiration = _unixEpoch.AddSeconds(expInt),
PayloadData = payloadData
};
nbfInt = Convert.ToInt32(nbfObj);
}
catch
{
throw new SignatureVerificationException("Claim 'nbf' must be an integer.");
}

if (secondsSinceEpoch < nbfInt)
{
throw new SignatureVerificationException("Token is not yet valid.");
}
}
}
}
Expand Down
25 changes: 22 additions & 3 deletions tests/JWT.Tests/DecodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,32 @@ public void DecodeToObject_Should_Throw_Exception_On_Invalid_Expiration_Claim()
[Fact]
public void DecodeToObject_Should_Throw_Exception_On_Expired_Claim()
{
var hourAgo = DateTime.UtcNow.Subtract(new TimeSpan(1, 0, 0));
var unixTimestamp = (int)(hourAgo - new DateTime(1970, 1, 1)).TotalSeconds;
var expiredtoken = JsonWebToken.Encode(new { exp = unixTimestamp }, "ABC", JwtHashAlgorithm.HS256);
var exp = (int)(DateTime.UtcNow.AddHours(-1) - JwtValidator.UnixEpoch).TotalSeconds;
var expiredtoken = JsonWebToken.Encode(new { exp = exp }, "ABC", JwtHashAlgorithm.HS256);

Action action = () => JsonWebToken.DecodeToObject<Customer>(expiredtoken, "ABC", verify: true);

action.ShouldThrow<TokenExpiredException>();
}

[Fact]
public void DecodeToObject_Should_Throw_Exception_Before_NotBefore_Becomes_Valid()
{
var nbf = (int)(DateTime.UtcNow.AddHours(1) - JwtValidator.UnixEpoch).TotalSeconds;
var invalidnbftoken = JsonWebToken.Encode(new { nbf = nbf }, "ABC", JwtHashAlgorithm.HS256);

Action action = () => JsonWebToken.DecodeToObject<Customer>(invalidnbftoken, "ABC", verify: true);

action.ShouldThrow<SignatureVerificationException>();
}

[Fact]
public void DecodeToObject_Should_Decode_Token_After_NotBefore_Becomes_Valid()
{
var nbf = (int)(DateTime.UtcNow - JwtValidator.UnixEpoch).TotalSeconds;
var validnbftoken = JsonWebToken.Encode(new { nbf = nbf }, "ABC", JwtHashAlgorithm.HS256);

JsonWebToken.DecodeToObject<Customer>(validnbftoken, "ABC", verify: true);
}
}
}
39 changes: 36 additions & 3 deletions tests/JWT.Tests/JwtDecoderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,48 @@ public void DecodeToObject_Should_Throw_Exception_On_Expired_Claim()
var decoder = new JwtDecoder(serializer, validator, urlEncoder);

var now = dateTimeProvider.GetNow();
var hourAgo = now.Subtract(new TimeSpan(1, 0, 0));
var unixTimestamp = (int)(hourAgo - new DateTime(1970, 1, 1)).TotalSeconds;
var exp = (int)(now.AddHours(-1) - JwtValidator.UnixEpoch).TotalSeconds;

var encoder = new JwtEncoder(new HMACSHA256Algorithm(), serializer, urlEncoder);
var expiredtoken = encoder.Encode(new { exp = unixTimestamp }, "ABC");
var expiredtoken = encoder.Encode(new { exp = exp }, "ABC");

Action action = () => decoder.DecodeToObject<Customer>(expiredtoken, "ABC", verify: true);

action.ShouldThrow<TokenExpiredException>();
}

[Fact]
public void DecodeToObject_Should_Throw_Exception_Before_NotBefore_Becomes_Valid()
{
var serializer = new JsonNetSerializer();
var dateTimeProvider = new UtcDateTimeProvider();
var validator = new JwtValidator(serializer, dateTimeProvider);
var urlEncoder = new JwtBase64UrlEncoder();
var decoder = new JwtDecoder(serializer, validator, urlEncoder);

var encoder = new JwtEncoder(new HMACSHA256Algorithm(), serializer, urlEncoder);
var nbf = (int)(DateTime.UtcNow.AddHours(1) - JwtValidator.UnixEpoch).TotalSeconds;
var invalidnbftoken = encoder.Encode(new { nbf = nbf }, "ABC");

Action action = () => decoder.DecodeToObject<Customer>(invalidnbftoken, "ABC", verify: true);

action.ShouldThrow<SignatureVerificationException>();
}

[Fact]
public void DecodeToObject_Should_Decode_Token_After_NotBefore_Becomes_Valid()
{
var serializer = new JsonNetSerializer();
var dateTimeProvider = new UtcDateTimeProvider();
var validator = new JwtValidator(serializer, dateTimeProvider);
var urlEncoder = new JwtBase64UrlEncoder();
var decoder = new JwtDecoder(serializer, validator, urlEncoder);

var encoder = new JwtEncoder(new HMACSHA256Algorithm(), serializer, urlEncoder);
var nbf = (int)(DateTime.UtcNow - JwtValidator.UnixEpoch).TotalSeconds;
var validnbftoken = encoder.Encode(new { nbf = nbf }, "ABC");

decoder.DecodeToObject<Customer>(validnbftoken, "ABC", verify: true);
}
}
}

0 comments on commit ac3568a

Please sign in to comment.