Skip to content

Commit

Permalink
Merge pull request jwt-dotnet#19 from abatishchev/refactoring-1
Browse files Browse the repository at this point in the history
General code refactoring
  • Loading branch information
mikelehen committed Jul 12, 2015
2 parents f032ce8 + 43bf662 commit cfad0ab
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 66 deletions.
42 changes: 24 additions & 18 deletions JWT.Tests/DecodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@
namespace JWT.Tests
{
[TestClass]

public class DecodeTests
{
JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.cr0xw8c_HKzhFBMQrseSPGoJ0NPlRp_3BKzP96jwBdY";
Customer customer = new Customer() { FirstName = "Bob", Age = 37 };
private static readonly Customer customer = new Customer { FirstName = "Bob", Age = 37 };

private Dictionary<string, object> dictionaryPayload = new Dictionary<string, object>() {
private const string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.cr0xw8c_HKzhFBMQrseSPGoJ0NPlRp_3BKzP96jwBdY";
private const string malformedtoken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.cr0xw8c_HKzhFBMQrseSPGoJ0NPlRp_3BKzP96jwBdY";

private static readonly IDictionary<string, object> dictionaryPayload = new Dictionary<string, object>
{
{ "FirstName", "Bob" },
{ "Age", 37 }
};

[TestMethod]
public void Should_Decode_Token_To_Json_Encoded_String()
{
var jsonSerializer = new JavaScriptSerializer();
var expectedPayload = jsonSerializer.Serialize(customer);

string decodedPayload = JsonWebToken.Decode(token, "ABC", false);
Expand All @@ -34,7 +36,7 @@ public void Should_Decode_Token_To_Dictionary()
{
object decodedPayload = JsonWebToken.DecodeToObject(token, "ABC", false);

decodedPayload.ShouldBeEquivalentTo(dictionaryPayload, options=>options.IncludingAllRuntimeProperties());
decodedPayload.ShouldBeEquivalentTo(dictionaryPayload, options => options.IncludingAllRuntimeProperties());
}

[TestMethod]
Expand All @@ -48,14 +50,15 @@ public void Should_Decode_Token_To_Dictionary_With_ServiceStack()
}

[TestMethod]
public void Should_Decode_Token_To_Dictionary_With_Newtonsoft() {
public void Should_Decode_Token_To_Dictionary_With_Newtonsoft()
{
JsonWebToken.JsonSerializer = new NewtonJsonSerializer();

object decodedPayload = JsonWebToken.DecodeToObject(token, "ABC", false);

decodedPayload.ShouldBeEquivalentTo(dictionaryPayload, options => options.IncludingAllRuntimeProperties());
}

[TestMethod]
public void Should_Decode_Token_To_Generic_Type()
{
Expand All @@ -65,7 +68,8 @@ public void Should_Decode_Token_To_Generic_Type()
}

[TestMethod]
public void Should_Decode_Token_To_Generic_Type_With_ServiceStack() {
public void Should_Decode_Token_To_Generic_Type_With_ServiceStack()
{
JsonWebToken.JsonSerializer = new ServiceStackJsonSerializer();

Customer decodedPayload = JsonWebToken.DecodeToObject<Customer>(token, "ABC", false);
Expand All @@ -74,7 +78,8 @@ public void Should_Decode_Token_To_Generic_Type_With_ServiceStack() {
}

[TestMethod]
public void Should_Decode_Token_To_Generic_Type_With_Newtonsoft() {
public void Should_Decode_Token_To_Generic_Type_With_Newtonsoft()
{
JsonWebToken.JsonSerializer = new NewtonJsonSerializer();

Customer decodedPayload = JsonWebToken.DecodeToObject<Customer>(token, "ABC", false);
Expand All @@ -84,9 +89,8 @@ public void Should_Decode_Token_To_Generic_Type_With_Newtonsoft() {

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void Should_Throw_On_Malformed_Token() {
string malformedtoken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.cr0xw8c_HKzhFBMQrseSPGoJ0NPlRp_3BKzP96jwBdY";

public void Should_Throw_On_Malformed_Token()
{
JsonWebToken.DecodeToObject<Customer>(malformedtoken, "ABC", false);
}

Expand Down Expand Up @@ -115,14 +119,16 @@ public void Should_Throw_On_Expired_Token()
var anHourAgoUtc = DateTime.UtcNow.Subtract(new TimeSpan(1, 0, 0));
Int32 unixTimestamp = (Int32)(anHourAgoUtc.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;

var invalidexptoken = JsonWebToken.Encode(new { exp=unixTimestamp }, "ABC", JwtHashAlgorithm.HS256);
var invalidexptoken = JsonWebToken.Encode(new { exp = unixTimestamp }, "ABC", JwtHashAlgorithm.HS256);

JsonWebToken.DecodeToObject<Customer>(invalidexptoken, "ABC", true);
}
}

public class Customer {
public string FirstName {get;set;}
public int Age {get;set;}
public class Customer
{
public string FirstName { get; set; }

public int Age { get; set; }
}
}
}
28 changes: 16 additions & 12 deletions JWT.Tests/EncodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ namespace JWT.Tests
[TestClass]
public class EncodeTests
{
string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.cr0xw8c_HKzhFBMQrseSPGoJ0NPlRp_3BKzP96jwBdY";
string extraheaderstoken = "eyJmb28iOiJiYXIiLCJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.slrbXF9VSrlX7LKsV-Umb_zEzWLxQjCfUOjNTbvyr1g";
Customer customer = new Customer() { FirstName = "Bob", Age = 37 };
private readonly static Customer customer = new Customer { FirstName = "Bob", Age = 37 };

private const string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.cr0xw8c_HKzhFBMQrseSPGoJ0NPlRp_3BKzP96jwBdY";
private const string extraheaderstoken = "eyJmb28iOiJiYXIiLCJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.slrbXF9VSrlX7LKsV-Umb_zEzWLxQjCfUOjNTbvyr1g";

[TestMethod]
public void Should_Encode_Type()
Expand All @@ -21,8 +22,8 @@ public void Should_Encode_Type()
[TestMethod]
public void Should_Encode_Type_With_Extra_Headers()
{
var extraheaders = new Dictionary<string, object>() { {"foo", "bar"} };
var extraheaders = new Dictionary<string, object> { { "foo", "bar" } };

string result = JsonWebToken.Encode(extraheaders, customer, "ABC", JwtHashAlgorithm.HS256);

Assert.AreEqual(extraheaderstoken, result);
Expand All @@ -38,31 +39,34 @@ public void Should_Encode_Type_With_ServiceStack()
}

[TestMethod]
public void Should_Encode_Type_With_ServiceStack_And_Extra_Headers() {
public void Should_Encode_Type_With_ServiceStack_And_Extra_Headers()
{
JsonWebToken.JsonSerializer = new ServiceStackJsonSerializer();
var extraheaders = new Dictionary<string, object>() { { "foo", "bar" } };

var extraheaders = new Dictionary<string, object> { { "foo", "bar" } };
string result = JsonWebToken.Encode(extraheaders, customer, "ABC", JwtHashAlgorithm.HS256);

Assert.AreEqual(extraheaderstoken, result);
}

[TestMethod]
public void Should_Encode_Type_With_Newtonsoft_Serializer() {
public void Should_Encode_Type_With_Newtonsoft_Serializer()
{
JsonWebToken.JsonSerializer = new NewtonJsonSerializer();
string result = JsonWebToken.Encode(customer, "ABC", JwtHashAlgorithm.HS256);

Assert.AreEqual(token, result);
}

[TestMethod]
public void Should_Encode_Type_With_Newtonsoft_Serializer_And_Extra_Headers() {
public void Should_Encode_Type_With_Newtonsoft_Serializer_And_Extra_Headers()
{
JsonWebToken.JsonSerializer = new NewtonJsonSerializer();

var extraheaders = new Dictionary<string, object>() { { "foo", "bar" } };
var extraheaders = new Dictionary<string, object> { { "foo", "bar" } };
string result = JsonWebToken.Encode(extraheaders, customer, "ABC", JwtHashAlgorithm.HS256);

Assert.AreEqual(extraheaderstoken, result);
}
}
}
}
74 changes: 38 additions & 36 deletions JWT/JWT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ public enum JwtHashAlgorithm
/// </summary>
public static class JsonWebToken
{
private static Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms;
private static readonly IDictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms;

/// <summary>
/// Pluggable JSON Serializer
/// </summary>
public static IJsonSerializer JsonSerializer = new DefaultJsonSerializer();

private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

static JsonWebToken()
{
HashAlgorithms = new Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>>
Expand All @@ -47,8 +49,8 @@ public static string Encode(IDictionary<string, object> extraHeaders, object pay
var segments = new List<string>();
var header = new Dictionary<string, object>(extraHeaders)
{
{"typ", "JWT"},
{"alg", algorithm.ToString()}
{ "typ", "JWT" },
{ "alg", algorithm.ToString() }
};

byte[] headerBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(header));
Expand Down Expand Up @@ -121,7 +123,7 @@ public static string Decode(string token, byte[] key, bool verify = true)
}
var header = parts[0];
var payload = parts[1];
byte[] crypto = Base64UrlDecode(parts[2]);
var crypto = Base64UrlDecode(parts[2]);

var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
Expand All @@ -137,32 +139,40 @@ public static string Decode(string token, byte[] key, bool verify = true)
var decodedCrypto = Convert.ToBase64String(crypto);
var decodedSignature = Convert.ToBase64String(signature);

if (decodedCrypto != decodedSignature)
Verify(decodedCrypto, decodedSignature, payloadJson);
}

return payloadJson;
}

private static void Verify(string decodedCrypto, string decodedSignature, string payloadJson)
{
if (decodedCrypto != decodedSignature)
{
throw new SignatureVerificationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
}

// 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);
if (payloadData.ContainsKey("exp") && payloadData["exp"] != null)
{
// safely unpack a boxed int
int exp;
try
{
exp = Convert.ToInt32(payloadData["exp"]);
}
catch (Exception)
{
throw new SignatureVerificationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
throw new SignatureVerificationException("Claim 'exp' must be an integer.");
}

// 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);
if (payloadData.ContainsKey("exp") && payloadData["exp"] != null)
var secondsSinceEpoch = Math.Round((DateTime.UtcNow - UnixEpoch).TotalSeconds);
if (secondsSinceEpoch >= exp)
{
// safely unpack a boxed int
int exp;
try { exp = Convert.ToInt32(payloadData["exp"]); }
catch (Exception)
{
throw new SignatureVerificationException("Claim 'exp' must be an integer.");
}

var secondsSinceEpoch = Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds);
if (secondsSinceEpoch >= exp)
{
throw new SignatureVerificationException("Token has expired.");
}
throw new SignatureVerificationException("Token has expired.");
}
}

return payloadJson;
}

/// <summary>
Expand All @@ -188,7 +198,7 @@ public static string Decode(string token, string key, bool verify = true)
/// <exception cref="SignatureVerificationException">Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm.</exception>
public static object DecodeToObject(string token, byte[] key, bool verify = true)
{
var payloadJson = JsonWebToken.Decode(token, key, verify);
var payloadJson = Decode(token, key, verify);
var payloadData = JsonSerializer.Deserialize<Dictionary<string, object>>(payloadJson);
return payloadData;
}
Expand Down Expand Up @@ -217,7 +227,7 @@ public static object DecodeToObject(string token, string key, bool verify = true
/// <exception cref="SignatureVerificationException">Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm.</exception>
public static T DecodeToObject<T>(string token, byte[] key, bool verify = true)
{
var payloadJson = JsonWebToken.Decode(token, key, verify);
var payloadJson = Decode(token, key, verify);
var payloadData = JsonSerializer.Deserialize<T>(payloadJson);
return payloadData;
}
Expand Down Expand Up @@ -267,19 +277,11 @@ public static byte[] Base64UrlDecode(string input)
{
case 0: break; // No pad chars in this case
case 2: output += "=="; break; // Two pad chars
case 3: output += "="; break; // One pad char
default: throw new System.Exception("Illegal base64url string!");
case 3: output += "="; break; // One pad char
default: throw new Exception("Illegal base64url string!");
}
var converted = Convert.FromBase64String(output); // Standard base64 decoder
return converted;
}
}

public class SignatureVerificationException : Exception
{
public SignatureVerificationException(string message)
: base(message)
{
}
}
}
1 change: 1 addition & 0 deletions JWT/JWT.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<Compile Include="DefaultJsonSerializer.cs" />
<Compile Include="JWT.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SignatureVerificationException.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
12 changes: 12 additions & 0 deletions JWT/SignatureVerificationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace JWT
{
public class SignatureVerificationException : Exception
{
public SignatureVerificationException(string message)
: base(message)
{
}
}
}

0 comments on commit cfad0ab

Please sign in to comment.