Created
December 15, 2019 19:25
-
-
Save Tempest1000/e22bdb9ee6d336da34b1648855899439 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Query Kind="Program"> | |
<Reference Relative="bin\Newtonsoft.Json.dll">D:\Data\LINQ\bin\Newtonsoft.Json.dll</Reference> | |
</Query> | |
void Main() | |
{ | |
// Note, the code below comes from https://github.com/auth0/auth0-aspnet/ | |
// This is code I was working through when learning how JSON Web Tokens work. | |
// JWT's can be tested using Auth0's excellent website here: https://jwt.io/ | |
// Note: for simplicity I have modified sections of the code, for a full implementation use Auth0's library | |
string header = @" | |
{ | |
""typ"": ""JWT"", | |
""alg"": ""HS256"" | |
} | |
"; | |
string issuedAt = SecondsSinceEpoch().ToString(); | |
issuedAt.Dump(); | |
double secondsValid = 1200d; // JWT is valid for 20 minutes | |
Dictionary<string, string> payloadDictionary = new Dictionary<string, string> | |
{ | |
{ "iss", "http://myapi.com" }, | |
{ "user", "sample_user" }, | |
{ "sub", "1234567890" }, | |
{ "iat", SecondsSinceEpoch().ToString() }, | |
{ "exp", EpochPlusSeconds(secondsValid).ToString() } | |
}; | |
// requires a download of Newtonsoft JSON from https://www.newtonsoft.com/json | |
// then F4 > browse > choose Newtonsoft.Json.dll | |
string payload = Newtonsoft.Json.JsonConvert.SerializeObject(payloadDictionary, Newtonsoft.Json.Formatting.Indented); | |
payload.Dump("JSON Payload"); | |
string secret = "secret"; | |
string jwt = Encode(header, payload, secret); | |
jwt.Dump("JWT"); | |
var decoded = Decode(jwt, Encoding.UTF8.GetBytes(secret), true); | |
decoded.Dump("Decoded JWT"); | |
} | |
string Encode(string header, string payload, string secret) | |
{ | |
header = header.Trim(); | |
payload = payload.Trim(); | |
var headerBytes = Encoding.UTF8.GetBytes(header); | |
var payloadBytes = Encoding.UTF8.GetBytes(payload); | |
var key = Encoding.UTF8.GetBytes(secret); | |
// encode | |
var encodedHeader = Convert.ToBase64String(headerBytes); | |
var encodedPayload = Convert.ToBase64String(payloadBytes); | |
var segments = new List<string>(); | |
segments.Add(Base64UrlEncode(headerBytes)); | |
segments.Add(Base64UrlEncode(payloadBytes)); | |
segments.Dump("Encoded Header and Payload"); | |
var stringToSign = string.Join(".", segments.ToArray()); | |
var bytesToSign = Encoding.UTF8.GetBytes(stringToSign); | |
stringToSign.Dump("String to sign"); | |
byte[] encryptedSignature; | |
// encrypt | |
using (var sha = new System.Security.Cryptography.HMACSHA256(key)) | |
{ | |
encryptedSignature = sha.ComputeHash(bytesToSign); | |
} | |
segments.Add(Base64UrlEncode(encryptedSignature)); | |
//signature.Dump("Encrypted Signature"); | |
string signature = string.Join(".", segments.ToArray()); | |
return signature; | |
} | |
/// <summary> | |
/// Given a JWT, decode it and return the JSON payload. | |
/// </summary> | |
/// <param name="token">The JWT.</param> | |
/// <param name="key">The key bytes that were used to sign the JWT.</param> | |
/// <param name="verify">Whether to verify the signature (default is true).</param> | |
/// <returns>A string containing the JSON payload.</returns> | |
/// <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> | |
/// <exception cref="TokenExpiredException">Thrown if the verify parameter was true and the token has an expired exp claim.</exception> | |
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 delimited by dot parts"); | |
} | |
var payload = parts[1]; | |
var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload)); | |
if (verify) | |
{ | |
Verify(payload, payloadJson, parts, key); | |
} | |
return payloadJson; | |
} | |
string Base64UrlEncode(byte[] input) | |
{ | |
var output = Convert.ToBase64String(input); | |
output = output.Split('=')[0]; // Remove any trailing '='s | |
output = output.Replace('+', '-'); // 62nd char of encoding | |
output = output.Replace('/', '_'); // 63rd char of encoding | |
return output; | |
} | |
byte[] Base64UrlDecode(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 | |
{ | |
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 FormatException("Illegal base64url string!"); | |
} | |
var converted = Convert.FromBase64String(output); // Standard base64 decoder | |
return converted; | |
} | |
void Verify(string payload, string payloadJson, string[] parts, byte[] key) | |
{ | |
// get the signature (encrypted) | |
var crypto = Base64UrlDecode(parts[2]); | |
var decodedCrypto = Convert.ToBase64String(crypto); | |
// get the header (encoded cleartext) | |
var header = parts[0]; | |
// combine header (encoded cleartext) + payload (encoded cleartext) | |
var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload)); | |
byte[] signatureData; | |
// encrypt combined encoded header + encoded payload | |
using (var sha = new System.Security.Cryptography.HMACSHA256(key)) | |
{ | |
signatureData = sha.ComputeHash(bytesToSign); | |
} | |
// encode the encrypted header + payload | |
var decodedSignature = Convert.ToBase64String(signatureData); | |
// compare the encoded (and encrypted) header + payload to make sure they match the signature that was passed, if not the signature is not valid | |
Verify(payloadJson, decodedCrypto, decodedSignature); | |
} | |
void Verify(string payloadJson, string decodedCrypto, string decodedSignature) | |
{ | |
if (decodedCrypto != decodedSignature) | |
{ | |
throw new Exception("Invalid signature"); | |
} | |
decodedCrypto.Dump("decodedCrypto"); | |
decodedSignature.Dump("decodedSignature"); | |
// requires a download of Newtonsoft JSON from https://www.newtonsoft.com/json | |
// then F4 > browse > choose Newtonsoft.Json.dll | |
// verify exp claim https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.4 | |
var payloadData = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(payloadJson); | |
object expObj; | |
if (!payloadData.TryGetValue("exp", out expObj) || expObj == null) | |
{ | |
return; | |
} | |
double expValue = 0d; | |
try | |
{ | |
expValue = Convert.ToDouble(expObj); | |
} | |
catch (FormatException) | |
{ | |
"Claim 'exp' must be an double.".Dump(); | |
//throw new FormatException("Claim 'exp' must be an integer."); | |
} | |
var secondsSinceEpoch = SecondsSinceEpoch(); | |
if (secondsSinceEpoch >= expValue) | |
{ | |
"Token Expired".Dump(); | |
(secondsSinceEpoch - expValue).Dump("Expired for"); | |
//throw new Exception("Token has expired."); | |
} | |
} | |
double SecondsSinceEpoch() | |
{ | |
return Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds); | |
} | |
double EpochPlusSeconds(double seconds) | |
{ | |
return SecondsSinceEpoch() + seconds; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment