diff --git a/src/JsonWebTokens/JsonWebToken.cs b/src/JsonWebTokens/JsonWebToken.cs index ac00d6f..4f7da09 100644 --- a/src/JsonWebTokens/JsonWebToken.cs +++ b/src/JsonWebTokens/JsonWebToken.cs @@ -6,7 +6,7 @@ namespace Jwt { /// - /// Provides methods for encoding and decoding JSON Web Tokens. + /// Encoding and decoding for JSON Web Tokens. /// public static class JsonWebToken { @@ -17,6 +17,15 @@ public static class JsonWebToken private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + /// + /// Creates a JWT using the specified payload and key hashed with . + /// + /// An arbitrary payload (must be serializable to JSON). + /// The key bytes used to sign the token. + /// The generated JWT. + public static string Encode(object payload, byte[] key) + => Encode(new JwtData { Payload = payload, KeyBytes = key, Algorithm = JwtHashAlgorithm.HS256 }); + /// /// Creates a JWT using the specified payload, key and algorithm. /// @@ -25,9 +34,16 @@ public static class JsonWebToken /// The hash algorithm to use. /// The generated JWT. public static string Encode(object payload, byte[] key, JwtHashAlgorithm algorithm) - { - return Encode(new JwtData { Payload = payload, KeyBytes = key, Algorithm = algorithm }); - } + => Encode(new JwtData { Payload = payload, KeyBytes = key, Algorithm = algorithm }); + + /// + /// Creates a JWT using the specified payload and key hashed with . + /// + /// An arbitrary payload (must be serializable to JSON). + /// The key used to sign the token. + /// The generated JWT. + public static string Encode(object payload, string key) + => Encode(new JwtData { Payload = payload, Key = key, Algorithm = JwtHashAlgorithm.HS256 }); /// /// Creates a JWT using the specified payload, key and algorithm. @@ -37,9 +53,7 @@ public static string Encode(object payload, byte[] key, JwtHashAlgorithm algorit /// The hash algorithm to use. /// The generated JWT. public static string Encode(object payload, string key, JwtHashAlgorithm algorithm) - { - return Encode(new JwtData { Payload = payload, Key = key, Algorithm = algorithm }); - } + => Encode(new JwtData { Payload = payload, Key = key, Algorithm = algorithm }); /// /// Creates a JWT using the specified . @@ -92,17 +106,15 @@ public static string Encode(JwtData data) /// When the given doesn't consist of 3 parts delimited by dots. /// public static string Decode(string token, string key, bool verify = true) - { - return Decode(token, Encoding.UTF8.GetBytes(key), verify); - } + => Decode(token, Encoding.UTF8.GetBytes(key), verify); /// - /// Decodes the specified JWT and returns the JSON payload. + /// Decodes the JWT token and deserializes JSON payload to a dictionary. /// /// The JWT. - /// The key bytes that were used to sign the JWT. + /// The key that was used to sign the JWT. /// Whether to verify the signature (default is true). - /// A string containing the JSON payload. + /// An object representing the payload. /// /// If the parameter was true and the signature was not valid /// or if the JWT was signed with an unsupported algorithm. @@ -110,43 +122,11 @@ public static string Decode(string token, string key, bool verify = true) /// /// When the given doesn't consist of 3 parts delimited by dots. /// - public static string Decode(string token, byte[] key, bool verify = true) - { - var parts = token.Split('.'); - if (parts.Length != 3) - { - throw new ArgumentException($"Token must consist of 3 parts delimited by dot. Given token: '{token}'."); - } - - var header = parts[0]; - var payload = parts[1]; - var crypto = Base64UrlDecode(parts[2]); - - var headerBytes = Base64UrlDecode(header); - var headerJson = Encoding.UTF8.GetString(headerBytes, 0, headerBytes.Length); - - var payloadBytes = Base64UrlDecode(payload); - var payloadJson = Encoding.UTF8.GetString(payloadBytes, 0, payloadBytes.Length); - - var headerData = JsonSerializer.Deserialize>(headerJson); - - if (verify) - { - var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload)); - var algorithm = (string)headerData["alg"]; - - var signature = ComputeHash(GetHashAlgorithm(algorithm), key, bytesToSign); - var decodedCrypto = Convert.ToBase64String(crypto); - var decodedSignature = Convert.ToBase64String(signature); - - Verify(decodedCrypto, decodedSignature, payloadJson); - } - - return payloadJson; - } + public static Dictionary DecodeToObject(string token, string key, bool verify = true) + => DecodeToObject>(token, key, verify); /// - /// Decodes the JWT token and deserializes JSON payload to an . + /// Decodes the JWT token and deserializes JSON payload to a dictionary. /// /// The JWT. /// The key that was used to sign the JWT. @@ -159,14 +139,13 @@ public static string Decode(string token, byte[] key, bool verify = true) /// /// When the given doesn't consist of 3 parts delimited by dots. /// - public static object DecodeToObject(string token, string key, bool verify = true) - { - return DecodeToObject(token, Encoding.UTF8.GetBytes(key), verify); - } + public static Dictionary DecodeToObject(string token, byte[] key, bool verify = true) + => DecodeToObject>(token, key, verify); /// - /// Decodes the JWT token and deserializes JSON payload to an . + /// Decodes the JWT token and deserializes JSON payload to . /// + /// The type of the object. /// The JWT. /// The key that was used to sign the JWT. /// Whether to verify the signature (default is true). @@ -178,17 +157,13 @@ public static object DecodeToObject(string token, string key, bool verify = true /// /// When the given doesn't consist of 3 parts delimited by dots. /// - public static object DecodeToObject(string token, byte[] key, bool verify = true) - { - var payloadJson = Decode(token, key, verify); - var payloadData = JsonSerializer.Deserialize>(payloadJson); - return payloadData; - } + public static T DecodeToObject(string token, string key, bool verify = true) + => DecodeToObject(token, Encoding.UTF8.GetBytes(key), verify); /// /// Decodes the JWT token and deserializes JSON payload to . /// - /// The type of the object. + /// The to return /// The JWT. /// The key that was used to sign the JWT. /// Whether to verify the signature (default is true). @@ -200,19 +175,16 @@ public static object DecodeToObject(string token, byte[] key, bool verify = true /// /// When the given doesn't consist of 3 parts delimited by dots. /// - public static T DecodeToObject(string token, string key, bool verify = true) - { - return DecodeToObject(token, Encoding.UTF8.GetBytes(key), verify); - } + public static T DecodeToObject(string token, byte[] key, bool verify = true) + => JsonSerializer.Deserialize(Decode(token, key, verify)); /// - /// Decodes the JWT token and deserializes JSON payload to . + /// Decodes the specified JWT and returns the JSON payload. /// - /// The to return /// The JWT. - /// The key that was used to sign the JWT. + /// The key bytes that were used to sign the JWT. /// Whether to verify the signature (default is true). - /// An object representing the payload. + /// A string containing the JSON payload. /// /// If the parameter was true and the signature was not valid /// or if the JWT was signed with an unsupported algorithm. @@ -220,33 +192,63 @@ public static T DecodeToObject(string token, string key, bool verify = true) /// /// When the given doesn't consist of 3 parts delimited by dots. /// - public static T DecodeToObject(string token, byte[] key, bool verify = true) + public static string Decode(string token, byte[] key, bool verify = true) { - var payloadJson = Decode(token, key, verify); - var payloadData = JsonSerializer.Deserialize(payloadJson); - return payloadData; + var parts = token.Split('.'); + if (parts.Length != 3) + { + throw new ArgumentException($"Token must consist of 3 parts delimited by dot. Given token: '{token}'.", nameof(token)); + } + + // Decode JWT payload + var payload = parts[1]; + var payloadBytes = Base64UrlDecode(payload); + var payloadJson = Encoding.UTF8.GetString(payloadBytes); + + if (verify) + { + // Decode JWT header. + var header = parts[0]; + var headerBytes = Base64UrlDecode(header); + var headerJson = Encoding.UTF8.GetString(headerBytes); + + // Decode the signature from the JWT. + var jwtSignature = UrlDecode(parts[2]); + + // Compute the signature for the JWT. + var headerData = JsonSerializer.Deserialize>(headerJson); + var algorithm = (string)headerData["alg"]; + var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload)); + var signature = ComputeHash(GetHashAlgorithm(algorithm), key, bytesToSign); + var computedSignature = Convert.ToBase64String(signature); + + Verify(jwtSignature, computedSignature, payloadJson); + } + + return payloadJson; } - private static void Verify(string decodedCrypto, string decodedSignature, string payloadJson) + private static void Verify(string jwtSignature, string computedSignature, string payloadJson) { - if (decodedCrypto != decodedSignature) + // Compare the signature from the JWT and the computed signature. + if (jwtSignature != computedSignature) { - throw new SignatureVerificationException($"Invalid signature. Expected '{decodedCrypto}' got '{decodedSignature}'."); + throw new SignatureVerificationException($"Invalid signature. Expected '{jwtSignature}' got '{computedSignature}'."); } // Verify exp claim: https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.4 var payloadData = JsonSerializer.Deserialize>(payloadJson); - if (payloadData.ContainsKey("exp") && payloadData["exp"] != null) + if (payloadData.TryGetValue("exp", out var expObj) && expObj != null) { // Safely unpack a boxed int. int exp; try { - exp = Convert.ToInt32(payloadData["exp"]); + exp = Convert.ToInt32(expObj); } catch (Exception) { - throw new SignatureVerificationException($"Claim 'exp' must be an integer. Given claim: '{payloadData["exp"]}'."); + throw new SignatureVerificationException($"Claim 'exp' must be an integer. Given claim: '{expObj}'."); } var secondsSinceEpoch = Math.Round((DateTime.UtcNow - UnixEpoch).TotalSeconds); @@ -292,25 +294,36 @@ private static JwtHashAlgorithm GetHashAlgorithm(string algorithm) case "HS512": return JwtHashAlgorithm.HS512; default: - throw new SignatureVerificationException("Algorithm not supported."); + throw new SignatureVerificationException($"Algorithm '{algorithm}' not supported."); } } + private static readonly char[] _padding = new [] { '=' }; + private static string Base64UrlEncode(byte[] input) { var output = Convert.ToBase64String(input); - output = output.Split('=')[0]; // Remove any trailing '='s + output = output.TrimEnd(_padding); // Remove any trailing '='s output = output.Replace('+', '-'); // 62nd char of encoding output = output.Replace('/', '_'); // 63rd char of encoding return output; } private static byte[] Base64UrlDecode(string input) + { + var output = UrlDecode(input); + var converted = Convert.FromBase64String(output); + return converted; + } + + private static string UrlDecode(string input) { var output = input; output = output.Replace('-', '+'); // 62nd char of encoding output = output.Replace('_', '/'); // 63rd char of encoding - switch (output.Length % 4) // Pad with trailing '='s + + // Pad with trailing '='s + switch (output.Length % 4) { case 0: break; // No pad chars in this case @@ -321,11 +334,10 @@ private static byte[] Base64UrlDecode(string input) output += "="; break; // One pad char default: - throw new Exception($"Illegal base64url string: '{input}'."); + throw new Exception($"Illegal base-64 string: '{input}'."); } - var converted = Convert.FromBase64String(output); - return converted; + return output; } } } diff --git a/src/JsonWebTokens/JsonWebTokens.csproj b/src/JsonWebTokens/JsonWebTokens.csproj index b205494..5509901 100644 --- a/src/JsonWebTokens/JsonWebTokens.csproj +++ b/src/JsonWebTokens/JsonWebTokens.csproj @@ -1,10 +1,10 @@ - JSON Web Tokens implementation for .NET Core. - Copyright 2017 Henk Mollema - 2.0.0 + JSON Web Tokens implementation for .NET Standard. + Copyright 2018 Henk Mollema + 3.0.0 Henk Mollema - netstandard1.3 + netstandard1.3;netstandard2.0 true JsonWebTokens JsonWebTokens @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/src/JsonWebTokens/JwtBuilder.cs b/src/JsonWebTokens/JwtBuilder.cs index d711f35..1fd51a1 100644 --- a/src/JsonWebTokens/JwtBuilder.cs +++ b/src/JsonWebTokens/JwtBuilder.cs @@ -95,9 +95,12 @@ public JwtBuilder WithHeaders(IDictionary dict) /// Builds the data to a object. /// /// A object. - public JwtData Build() - { - return _data; - } + public JwtData Build() => _data; + + /// + /// Builds and encodes the current object. + /// + /// An encoded JSON Web Token. + public string Encode() => JsonWebToken.Encode(Build()); } -} \ No newline at end of file +}