├── tools └── NuGet.exe ├── JWT ├── SignatureVerificationException.cs ├── IJsonSerializer.cs ├── DefaultJsonSerializer.cs ├── Properties │ └── AssemblyInfo.cs ├── JWT.csproj └── JWT.cs ├── .gitignore ├── JWT.Tests ├── packages.config ├── Serializers.cs ├── Properties │ └── AssemblyInfo.cs ├── EncodeTests.cs ├── DecodeTests.cs └── JWT.Tests.csproj ├── package.cmd ├── JWT.nuspec ├── LICENSE.txt ├── JWT.sln └── README.md /tools/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monry/JWT-for-Unity/HEAD/tools/NuGet.exe -------------------------------------------------------------------------------- /JWT/SignatureVerificationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JWT 4 | { 5 | public class SignatureVerificationException : Exception 6 | { 7 | public SignatureVerificationException(string message) 8 | : base(message) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files build by Visual Studio 5 | *.user 6 | *.vspscc 7 | *.suo 8 | *.bak 9 | [Bb]in 10 | [Dd]ebug*/ 11 | obj/ 12 | [Rr]elease*/ 13 | _ReSharper*/ 14 | [Tt]est[Rr]esult* 15 | package/ 16 | packages/ 17 | *.nupkg 18 | -------------------------------------------------------------------------------- /JWT.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /package.cmd: -------------------------------------------------------------------------------- 1 | tools\nuget.exe update -self 2 | 3 | if not exist package mkdir package 4 | if not exist package mkdir package 5 | if not exist package\lib mkdir package\lib 6 | if not exist package\lib\3.5 mkdir package\lib\3.5 7 | 8 | copy JWT\bin\Release\JWT.dll package\lib\3.5\ 9 | 10 | tools\nuget.exe pack JWT.nuspec -BasePath package -------------------------------------------------------------------------------- /JWT.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JWT 5 | 1.3.4 6 | John Sheehan, Michael Lehenbauer 7 | JWT (JSON Web Token) Implementation for .NET (Public Domain) 8 | en-US 9 | http://github.com/johnsheehan/jwt 10 | jwt json 11 | 12 | 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | JWT for .NET 2 | Written by John Sheehan 3 | (http://john-sheehan.com) 4 | 5 | This work is public domain. 6 | "The person who associated a work with this document has 7 | dedicated the work to the Commons by waiving all of his 8 | or her rights to the work worldwide under copyright law 9 | and all related or neighboring legal rights he or she 10 | had in the work, to the extent allowable by law." 11 | 12 | For more information, please visit: 13 | http://creativecommons.org/publicdomain/zero/1.0/ -------------------------------------------------------------------------------- /JWT/IJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace JWT 2 | { 3 | /// 4 | /// Provides JSON Serialize and Deserialize. Allows custom serializers used. 5 | /// 6 | public interface IJsonSerializer 7 | { 8 | /// 9 | /// Serialize an object to JSON string 10 | /// 11 | /// object 12 | /// JSON string 13 | string Serialize(object obj); 14 | 15 | /// 16 | /// Deserialize a JSON string to typed object. 17 | /// 18 | /// type of object 19 | /// JSON string 20 | /// typed object 21 | T Deserialize(string json); 22 | } 23 | } -------------------------------------------------------------------------------- /JWT.Tests/Serializers.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using JsonSerializer = ServiceStack.Text.JsonSerializer; 3 | 4 | namespace JWT.Tests 5 | { 6 | public class ServiceStackJsonSerializer : IJsonSerializer 7 | { 8 | public string Serialize(object obj) 9 | { 10 | return JsonSerializer.SerializeToString(obj); 11 | } 12 | 13 | public T Deserialize(string json) 14 | { 15 | return JsonSerializer.DeserializeFromString(json); 16 | } 17 | } 18 | 19 | public class NewtonJsonSerializer : IJsonSerializer 20 | { 21 | public string Serialize(object obj) 22 | { 23 | return JsonConvert.SerializeObject(obj); 24 | } 25 | 26 | public T Deserialize(string json) 27 | { 28 | return JsonConvert.DeserializeObject(json); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /JWT/DefaultJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace JWT 4 | { 5 | /// 6 | /// JSON Serializer using JavaScriptSerializer 7 | /// 8 | public class DefaultJsonSerializer : IJsonSerializer 9 | { 10 | /// 11 | /// Serialize an object to JSON string 12 | /// 13 | /// object 14 | /// JSON string 15 | public string Serialize(object obj) 16 | { 17 | return JsonUtility.ToJson(obj); 18 | } 19 | 20 | /// 21 | /// Deserialize a JSON string to typed object. 22 | /// 23 | /// type of object 24 | /// JSON string 25 | /// typed object 26 | public T Deserialize(string json) 27 | { 28 | return JsonUtility.FromJson(json); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /JWT.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWT", "JWT\JWT.csproj", "{A80B51B8-DDF6-4026-98A4-B59653E50B38}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWT.Tests", "JWT.Tests\JWT.Tests.csproj", "{BF568781-D576-4545-A552-4DC839B1AF14}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {A80B51B8-DDF6-4026-98A4-B59653E50B38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {A80B51B8-DDF6-4026-98A4-B59653E50B38}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {A80B51B8-DDF6-4026-98A4-B59653E50B38}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {A80B51B8-DDF6-4026-98A4-B59653E50B38}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {BF568781-D576-4545-A552-4DC839B1AF14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {BF568781-D576-4545-A552-4DC839B1AF14}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {BF568781-D576-4545-A552-4DC839B1AF14}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {BF568781-D576-4545-A552-4DC839B1AF14}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /JWT/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JWT")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Public Domain")] 12 | [assembly: AssemblyProduct("JWT")] 13 | [assembly: AssemblyCopyright("Public Domain")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("745e649e-588d-4e6d-81ce-1d2c39c24b0a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.3.4.0")] 36 | [assembly: AssemblyFileVersion("1.3.4.0")] 37 | -------------------------------------------------------------------------------- /JWT.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JWT.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JWT.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5dd5d960-dc7d-4dc4-97cb-1024d9184c85")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /JWT/JWT.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {A80B51B8-DDF6-4026-98A4-B59653E50B38} 9 | Library 10 | Properties 11 | JWT 12 | JWT 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | -------------------------------------------------------------------------------- /JWT.Tests/EncodeTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Collections.Generic; 3 | 4 | namespace JWT.Tests 5 | { 6 | [TestClass] 7 | public class EncodeTests 8 | { 9 | private readonly static Customer customer = new Customer { FirstName = "Bob", Age = 37 }; 10 | 11 | private const string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.cr0xw8c_HKzhFBMQrseSPGoJ0NPlRp_3BKzP96jwBdY"; 12 | private const string extraheaderstoken = "eyJmb28iOiJiYXIiLCJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.slrbXF9VSrlX7LKsV-Umb_zEzWLxQjCfUOjNTbvyr1g"; 13 | 14 | [TestMethod] 15 | public void Should_Encode_Type() 16 | { 17 | string result = JsonWebToken.Encode(customer, "ABC", JwtHashAlgorithm.HS256); 18 | 19 | Assert.AreEqual(token, result); 20 | } 21 | 22 | [TestMethod] 23 | public void Should_Encode_Type_With_Extra_Headers() 24 | { 25 | var extraheaders = new Dictionary { { "foo", "bar" } }; 26 | 27 | string result = JsonWebToken.Encode(extraheaders, customer, "ABC", JwtHashAlgorithm.HS256); 28 | 29 | Assert.AreEqual(extraheaderstoken, result); 30 | } 31 | 32 | [TestMethod] 33 | public void Should_Encode_Type_With_ServiceStack() 34 | { 35 | JsonWebToken.JsonSerializer = new ServiceStackJsonSerializer(); 36 | string result = JsonWebToken.Encode(customer, "ABC", JwtHashAlgorithm.HS256); 37 | 38 | Assert.AreEqual(token, result); 39 | } 40 | 41 | [TestMethod] 42 | public void Should_Encode_Type_With_ServiceStack_And_Extra_Headers() 43 | { 44 | JsonWebToken.JsonSerializer = new ServiceStackJsonSerializer(); 45 | 46 | var extraheaders = new Dictionary { { "foo", "bar" } }; 47 | string result = JsonWebToken.Encode(extraheaders, customer, "ABC", JwtHashAlgorithm.HS256); 48 | 49 | Assert.AreEqual(extraheaderstoken, result); 50 | } 51 | 52 | [TestMethod] 53 | public void Should_Encode_Type_With_Newtonsoft_Serializer() 54 | { 55 | JsonWebToken.JsonSerializer = new NewtonJsonSerializer(); 56 | string result = JsonWebToken.Encode(customer, "ABC", JwtHashAlgorithm.HS256); 57 | 58 | Assert.AreEqual(token, result); 59 | } 60 | 61 | [TestMethod] 62 | public void Should_Encode_Type_With_Newtonsoft_Serializer_And_Extra_Headers() 63 | { 64 | JsonWebToken.JsonSerializer = new NewtonJsonSerializer(); 65 | 66 | var extraheaders = new Dictionary { { "foo", "bar" } }; 67 | string result = JsonWebToken.Encode(extraheaders, customer, "ABC", JwtHashAlgorithm.HS256); 68 | 69 | Assert.AreEqual(extraheaderstoken, result); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON Web Token (JWT) Implementation for Unity (C\#) 2 | 3 | This library supports generating and decoding [JSON Web Tokens](http://tools.ietf.org/html/draft-jones-json-web-token-10). 4 | 5 | ## Installation 6 | The easiest way to install is via NuGet. See [here](https://nuget.org/packages/JWT). Else, you can download and compile it yourself. 7 | 8 | ## Usage 9 | ### Creating Tokens 10 | 11 | ```csharp 12 | var payload = new Dictionary() 13 | { 14 | { "claim1", 0 }, 15 | { "claim2", "claim2-value" } 16 | }; 17 | var secretKey = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; 18 | string token = JWT.JsonWebToken.Encode(payload, secretKey, JWT.JwtHashAlgorithm.HS256); 19 | Console.WriteLine(token); 20 | ``` 21 | 22 | Output will be: 23 | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGFpbTEiOjAsImNsYWltMiI6ImNsYWltMi12YWx1ZSJ9.8pwBI_HtXqI3UgQHQ_rDRnSQRxFL1SR8fbQoS-5kM5s 24 | 25 | ### Verifying and Decoding Tokens 26 | 27 | ```csharp 28 | var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGFpbTEiOjAsImNsYWltMiI6ImNsYWltMi12YWx1ZSJ9.8pwBI_HtXqI3UgQHQ_rDRnSQRxFL1SR8fbQoS-5kM5s"; 29 | var secretKey = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; 30 | try 31 | { 32 | string jsonPayload = JWT.JsonWebToken.Decode(token, secretKey); 33 | Console.WriteLine(jsonPayload); 34 | } 35 | catch (JWT.SignatureVerificationException) 36 | { 37 | Console.WriteLine("Invalid token!"); 38 | } 39 | ``` 40 | 41 | Output will be: 42 | 43 | {"claim1":0,"claim2":"claim2-value"} 44 | 45 | You can also deserialize the JSON payload directly to a .Net object with DecodeToObject: 46 | 47 | ```csharp 48 | var payload = JWT.JsonWebToken.DecodeToObject(token, secretKey) as IDictionary; 49 | Console.WriteLine(payload["claim2"]); 50 | ``` 51 | 52 | which will output: 53 | 54 | claim2-value 55 | 56 | #### exp claim 57 | 58 | As described in the [JWT RFC](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.4) the `exp` "claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing." If an `exp` claim is present and is prior to the current time the token will fail verification. The exp (expiry) value must be specified as the number of seconds since 1/1/1970 UTC. 59 | 60 | ```csharp 61 | var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 62 | var now = Math.Round((DateTime.UtcNow - unixEpoch).TotalSeconds); 63 | var payload = new Dictionary() 64 | { 65 | { "exp", now } 66 | }; 67 | var secretKey = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"; 68 | string token = JWT.JsonWebToken.Encode(payload, secretKey, JWT.JwtHashAlgorithm.HS256); 69 | 70 | string jsonPayload = JWT.JsonWebToken.Decode(token, secretKey); // JWT.SignatureVerificationException! 71 | ``` 72 | 73 | ### Configure JSON Serialization 74 | 75 | By default JSON Serialization is done by System.Web.Script.Serialization.JavaScriptSerializer. To configure a different one first implement the IJsonSerializer interface. 76 | 77 | ```csharp 78 | public class CustomJsonSerializer : IJsonSerializer 79 | { 80 | public string Serialize(object obj) 81 | { 82 | // Implement using favorite JSON Serializer 83 | } 84 | 85 | public T Deserialize(string json) 86 | { 87 | // Implement using favorite JSON Serializer 88 | } 89 | } 90 | ``` 91 | 92 | Next configure this serializer as the JsonSerializer. 93 | ```cs 94 | JsonWebToken.JsonSerializer = new CustomJsonSerializer(); 95 | ``` 96 | -------------------------------------------------------------------------------- /JWT.Tests/DecodeTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Web.Script.Serialization; 5 | using FluentAssertions; 6 | 7 | namespace JWT.Tests 8 | { 9 | [TestClass] 10 | public class DecodeTests 11 | { 12 | private static readonly Customer customer = new Customer { FirstName = "Bob", Age = 37 }; 13 | 14 | private const string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.cr0xw8c_HKzhFBMQrseSPGoJ0NPlRp_3BKzP96jwBdY"; 15 | private const string malformedtoken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9eyJGaXJzdE5hbWUiOiJCb2IiLCJBZ2UiOjM3fQ.cr0xw8c_HKzhFBMQrseSPGoJ0NPlRp_3BKzP96jwBdY"; 16 | 17 | private static readonly IDictionary dictionaryPayload = new Dictionary 18 | { 19 | { "FirstName", "Bob" }, 20 | { "Age", 37 } 21 | }; 22 | 23 | [TestMethod] 24 | public void Should_Decode_Token_To_Json_Encoded_String() 25 | { 26 | var jsonSerializer = new JavaScriptSerializer(); 27 | var expectedPayload = jsonSerializer.Serialize(customer); 28 | 29 | string decodedPayload = JsonWebToken.Decode(token, "ABC", false); 30 | 31 | Assert.AreEqual(expectedPayload, decodedPayload); 32 | } 33 | 34 | [TestMethod] 35 | public void Should_Decode_Token_To_Dictionary() 36 | { 37 | object decodedPayload = JsonWebToken.DecodeToObject(token, "ABC", false); 38 | 39 | decodedPayload.ShouldBeEquivalentTo(dictionaryPayload, options => options.IncludingAllRuntimeProperties()); 40 | } 41 | 42 | [TestMethod] 43 | public void Should_Decode_Token_To_Dictionary_With_ServiceStack() 44 | { 45 | JsonWebToken.JsonSerializer = new ServiceStackJsonSerializer(); 46 | 47 | object decodedPayload = JsonWebToken.DecodeToObject(token, "ABC", false); 48 | 49 | decodedPayload.ShouldBeEquivalentTo(dictionaryPayload, options => options.IncludingAllRuntimeProperties()); 50 | } 51 | 52 | [TestMethod] 53 | public void Should_Decode_Token_To_Dictionary_With_Newtonsoft() 54 | { 55 | JsonWebToken.JsonSerializer = new NewtonJsonSerializer(); 56 | 57 | object decodedPayload = JsonWebToken.DecodeToObject(token, "ABC", false); 58 | 59 | decodedPayload.ShouldBeEquivalentTo(dictionaryPayload, options => options.IncludingAllRuntimeProperties()); 60 | } 61 | 62 | [TestMethod] 63 | public void Should_Decode_Token_To_Generic_Type() 64 | { 65 | Customer decodedPayload = JsonWebToken.DecodeToObject(token, "ABC", false); 66 | 67 | decodedPayload.ShouldBeEquivalentTo(customer); 68 | } 69 | 70 | [TestMethod] 71 | public void Should_Decode_Token_To_Generic_Type_With_ServiceStack() 72 | { 73 | JsonWebToken.JsonSerializer = new ServiceStackJsonSerializer(); 74 | 75 | Customer decodedPayload = JsonWebToken.DecodeToObject(token, "ABC", false); 76 | 77 | decodedPayload.ShouldBeEquivalentTo(customer); 78 | } 79 | 80 | [TestMethod] 81 | public void Should_Decode_Token_To_Generic_Type_With_Newtonsoft() 82 | { 83 | JsonWebToken.JsonSerializer = new NewtonJsonSerializer(); 84 | 85 | Customer decodedPayload = JsonWebToken.DecodeToObject(token, "ABC", false); 86 | 87 | decodedPayload.ShouldBeEquivalentTo(customer); 88 | } 89 | 90 | [TestMethod] 91 | [ExpectedException(typeof(ArgumentException))] 92 | public void Should_Throw_On_Malformed_Token() 93 | { 94 | JsonWebToken.DecodeToObject(malformedtoken, "ABC", false); 95 | } 96 | 97 | [TestMethod] 98 | [ExpectedException(typeof(SignatureVerificationException))] 99 | public void Should_Throw_On_Invalid_Key() 100 | { 101 | string invalidkey = "XYZ"; 102 | 103 | JsonWebToken.DecodeToObject(token, invalidkey, true); 104 | } 105 | 106 | [TestMethod] 107 | [ExpectedException(typeof(SignatureVerificationException))] 108 | public void Should_Throw_On_Invalid_Expiration_Claim() 109 | { 110 | var invalidexptoken = JsonWebToken.Encode(new { exp = "asdsad" }, "ABC", JwtHashAlgorithm.HS256); 111 | 112 | JsonWebToken.DecodeToObject(invalidexptoken, "ABC", true); 113 | } 114 | 115 | [TestMethod] 116 | [ExpectedException(typeof(SignatureVerificationException))] 117 | public void Should_Throw_On_Expired_Token() 118 | { 119 | var anHourAgoUtc = DateTime.UtcNow.Subtract(new TimeSpan(1, 0, 0)); 120 | Int32 unixTimestamp = (Int32)(anHourAgoUtc.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; 121 | 122 | var invalidexptoken = JsonWebToken.Encode(new { exp = unixTimestamp }, "ABC", JwtHashAlgorithm.HS256); 123 | 124 | JsonWebToken.DecodeToObject(invalidexptoken, "ABC", true); 125 | } 126 | } 127 | 128 | public class Customer 129 | { 130 | public string FirstName { get; set; } 131 | 132 | public int Age { get; set; } 133 | } 134 | } -------------------------------------------------------------------------------- /JWT.Tests/JWT.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {BF568781-D576-4545-A552-4DC839B1AF14} 7 | Library 8 | Properties 9 | JWT.Tests 10 | JWT.Tests 11 | v4.5.1 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\packages\FluentAssertions.3.3.0\lib\net45\FluentAssertions.dll 40 | True 41 | 42 | 43 | ..\packages\FluentAssertions.3.3.0\lib\net45\FluentAssertions.Core.dll 44 | True 45 | 46 | 47 | False 48 | ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll 49 | 50 | 51 | ..\packages\ServiceStack.Text.4.0.40\lib\net40\ServiceStack.Text.dll 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {a80b51b8-ddf6-4026-98a4-b59653e50b38} 79 | JWT 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | False 90 | 91 | 92 | False 93 | 94 | 95 | False 96 | 97 | 98 | False 99 | 100 | 101 | 102 | 103 | 104 | 105 | 112 | -------------------------------------------------------------------------------- /JWT/JWT.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | 6 | namespace JWT 7 | { 8 | public enum JwtHashAlgorithm 9 | { 10 | HS256, 11 | HS384, 12 | HS512 13 | } 14 | 15 | /// 16 | /// Provides methods for encoding and decoding JSON Web Tokens. 17 | /// 18 | public static class JsonWebToken 19 | { 20 | private static readonly IDictionary> HashAlgorithms; 21 | 22 | /// 23 | /// Pluggable JSON Serializer 24 | /// 25 | public static IJsonSerializer JsonSerializer = new DefaultJsonSerializer(); 26 | 27 | private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); 28 | 29 | static JsonWebToken() 30 | { 31 | HashAlgorithms = new Dictionary> 32 | { 33 | { JwtHashAlgorithm.HS256, (key, value) => { using (var sha = new HMACSHA256(key)) { return sha.ComputeHash(value); } } }, 34 | { JwtHashAlgorithm.HS384, (key, value) => { using (var sha = new HMACSHA384(key)) { return sha.ComputeHash(value); } } }, 35 | { JwtHashAlgorithm.HS512, (key, value) => { using (var sha = new HMACSHA512(key)) { return sha.ComputeHash(value); } } } 36 | }; 37 | } 38 | 39 | /// 40 | /// Creates a JWT given a header, a payload, the signing key, and the algorithm to use. 41 | /// 42 | /// An arbitrary set of extra headers. Will be augmented with the standard "typ" and "alg" headers. 43 | /// An arbitrary payload (must be serializable to JSON via ). 44 | /// The key bytes used to sign the token. 45 | /// The hash algorithm to use. 46 | /// The generated JWT. 47 | public static string Encode(IDictionary extraHeaders, object payload, byte[] key, JwtHashAlgorithm algorithm) 48 | { 49 | var segments = new List(); 50 | var header = new Dictionary(extraHeaders) 51 | { 52 | { "typ", "JWT" }, 53 | { "alg", algorithm.ToString() } 54 | }; 55 | 56 | byte[] headerBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(header)); 57 | byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(payload)); 58 | 59 | segments.Add(Base64UrlEncode(headerBytes)); 60 | segments.Add(Base64UrlEncode(payloadBytes)); 61 | 62 | var stringToSign = string.Join(".", segments.ToArray()); 63 | 64 | var bytesToSign = Encoding.UTF8.GetBytes(stringToSign); 65 | 66 | byte[] signature = HashAlgorithms[algorithm](key, bytesToSign); 67 | segments.Add(Base64UrlEncode(signature)); 68 | 69 | return string.Join(".", segments.ToArray()); 70 | } 71 | 72 | /// 73 | /// Creates a JWT given a payload, the signing key, and the algorithm to use. 74 | /// 75 | /// An arbitrary payload (must be serializable to JSON via ). 76 | /// The key used to sign the token. 77 | /// The hash algorithm to use. 78 | /// The generated JWT. 79 | public static string Encode(object payload, byte[] key, JwtHashAlgorithm algorithm) 80 | { 81 | return Encode(new Dictionary(), payload, key, algorithm); 82 | } 83 | 84 | /// 85 | /// Creates a JWT given a set of arbitrary extra headers, a payload, the signing key, and the algorithm to use. 86 | /// 87 | /// An arbitrary set of extra headers. Will be augmented with the standard "typ" and "alg" headers. 88 | /// An arbitrary payload (must be serializable to JSON via ). 89 | /// The key bytes used to sign the token. 90 | /// The hash algorithm to use. 91 | /// The generated JWT. 92 | public static string Encode(IDictionary extraHeaders, object payload, string key, JwtHashAlgorithm algorithm) 93 | { 94 | return Encode(extraHeaders, payload, Encoding.UTF8.GetBytes(key), algorithm); 95 | } 96 | 97 | /// 98 | /// Creates a JWT given a payload, the signing key, and the algorithm to use. 99 | /// 100 | /// An arbitrary payload (must be serializable to JSON via ). 101 | /// The key used to sign the token. 102 | /// The hash algorithm to use. 103 | /// The generated JWT. 104 | public static string Encode(object payload, string key, JwtHashAlgorithm algorithm) 105 | { 106 | return Encode(new Dictionary(), payload, Encoding.UTF8.GetBytes(key), algorithm); 107 | } 108 | 109 | /// 110 | /// Given a JWT, decode it and return the JSON payload. 111 | /// 112 | /// The JWT. 113 | /// The key bytes that were used to sign the JWT. 114 | /// Whether to verify the signature (default is true). 115 | /// A string containing the JSON payload. 116 | /// Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm. 117 | public static string Decode(string token, byte[] key, bool verify = true) 118 | { 119 | var parts = token.Split('.'); 120 | if (parts.Length != 3) 121 | { 122 | throw new ArgumentException("Token must consist from 3 delimited by dot parts"); 123 | } 124 | var header = parts[0]; 125 | var payload = parts[1]; 126 | var crypto = Base64UrlDecode(parts[2]); 127 | 128 | var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header)); 129 | var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload)); 130 | 131 | var headerData = JsonSerializer.Deserialize>(headerJson); 132 | 133 | if (verify) 134 | { 135 | var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload)); 136 | var algorithm = (string)headerData["alg"]; 137 | 138 | var signature = HashAlgorithms[GetHashAlgorithm(algorithm)](key, bytesToSign); 139 | var decodedCrypto = Convert.ToBase64String(crypto); 140 | var decodedSignature = Convert.ToBase64String(signature); 141 | 142 | Verify(decodedCrypto, decodedSignature, payloadJson); 143 | } 144 | 145 | return payloadJson; 146 | } 147 | 148 | private static void Verify(string decodedCrypto, string decodedSignature, string payloadJson) 149 | { 150 | if (decodedCrypto != decodedSignature) 151 | { 152 | throw new SignatureVerificationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature)); 153 | } 154 | 155 | // verify exp claim https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.4 156 | var payloadData = JsonSerializer.Deserialize>(payloadJson); 157 | if (payloadData.ContainsKey("exp") && payloadData["exp"] != null) 158 | { 159 | // safely unpack a boxed int 160 | int exp; 161 | try 162 | { 163 | exp = Convert.ToInt32(payloadData["exp"]); 164 | } 165 | catch (Exception) 166 | { 167 | throw new SignatureVerificationException("Claim 'exp' must be an integer."); 168 | } 169 | 170 | var secondsSinceEpoch = Math.Round((DateTime.UtcNow - UnixEpoch).TotalSeconds); 171 | if (secondsSinceEpoch >= exp) 172 | { 173 | throw new SignatureVerificationException("Token has expired."); 174 | } 175 | } 176 | } 177 | 178 | /// 179 | /// Given a JWT, decode it and return the JSON payload. 180 | /// 181 | /// The JWT. 182 | /// The key that was used to sign the JWT. 183 | /// Whether to verify the signature (default is true). 184 | /// A string containing the JSON payload. 185 | /// Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm. 186 | public static string Decode(string token, string key, bool verify = true) 187 | { 188 | return Decode(token, Encoding.UTF8.GetBytes(key), verify); 189 | } 190 | 191 | /// 192 | /// Given a JWT, decode it and return the payload as an object (by deserializing it with ). 193 | /// 194 | /// The JWT. 195 | /// The key that was used to sign the JWT. 196 | /// Whether to verify the signature (default is true). 197 | /// An object representing the payload. 198 | /// Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm. 199 | public static object DecodeToObject(string token, byte[] key, bool verify = true) 200 | { 201 | var payloadJson = Decode(token, key, verify); 202 | var payloadData = JsonSerializer.Deserialize>(payloadJson); 203 | return payloadData; 204 | } 205 | 206 | /// 207 | /// Given a JWT, decode it and return the payload as an object (by deserializing it with ). 208 | /// 209 | /// The JWT. 210 | /// The key that was used to sign the JWT. 211 | /// Whether to verify the signature (default is true). 212 | /// An object representing the payload. 213 | /// Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm. 214 | public static object DecodeToObject(string token, string key, bool verify = true) 215 | { 216 | return DecodeToObject(token, Encoding.UTF8.GetBytes(key), verify); 217 | } 218 | 219 | /// 220 | /// Given a JWT, decode it and return the payload as an object (by deserializing it with ). 221 | /// 222 | /// The to return 223 | /// The JWT. 224 | /// The key that was used to sign the JWT. 225 | /// Whether to verify the signature (default is true). 226 | /// An object representing the payload. 227 | /// Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm. 228 | public static T DecodeToObject(string token, byte[] key, bool verify = true) 229 | { 230 | var payloadJson = Decode(token, key, verify); 231 | var payloadData = JsonSerializer.Deserialize(payloadJson); 232 | return payloadData; 233 | } 234 | 235 | /// 236 | /// Given a JWT, decode it and return the payload as an object (by deserializing it with ). 237 | /// 238 | /// The to return 239 | /// The JWT. 240 | /// The key that was used to sign the JWT. 241 | /// Whether to verify the signature (default is true). 242 | /// An object representing the payload. 243 | /// Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm. 244 | public static T DecodeToObject(string token, string key, bool verify = true) 245 | { 246 | return DecodeToObject(token, Encoding.UTF8.GetBytes(key), verify); 247 | } 248 | 249 | private static JwtHashAlgorithm GetHashAlgorithm(string algorithm) 250 | { 251 | switch (algorithm) 252 | { 253 | case "HS256": return JwtHashAlgorithm.HS256; 254 | case "HS384": return JwtHashAlgorithm.HS384; 255 | case "HS512": return JwtHashAlgorithm.HS512; 256 | default: throw new SignatureVerificationException("Algorithm not supported."); 257 | } 258 | } 259 | 260 | // from JWT spec 261 | public static string Base64UrlEncode(byte[] input) 262 | { 263 | var output = Convert.ToBase64String(input); 264 | output = output.Split('=')[0]; // Remove any trailing '='s 265 | output = output.Replace('+', '-'); // 62nd char of encoding 266 | output = output.Replace('/', '_'); // 63rd char of encoding 267 | return output; 268 | } 269 | 270 | // from JWT spec 271 | public static byte[] Base64UrlDecode(string input) 272 | { 273 | var output = input; 274 | output = output.Replace('-', '+'); // 62nd char of encoding 275 | output = output.Replace('_', '/'); // 63rd char of encoding 276 | switch (output.Length % 4) // Pad with trailing '='s 277 | { 278 | case 0: break; // No pad chars in this case 279 | case 2: output += "=="; break; // Two pad chars 280 | case 3: output += "="; break; // One pad char 281 | default: throw new Exception("Illegal base64url string!"); 282 | } 283 | var converted = Convert.FromBase64String(output); // Standard base64 decoder 284 | return converted; 285 | } 286 | } 287 | } 288 | --------------------------------------------------------------------------------