├── 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 |
--------------------------------------------------------------------------------