Skip to content

Instantly share code, notes, and snippets.

@Tempest1000
Created December 15, 2019 19:25
Show Gist options
  • Save Tempest1000/e22bdb9ee6d336da34b1648855899439 to your computer and use it in GitHub Desktop.
Save Tempest1000/e22bdb9ee6d336da34b1648855899439 to your computer and use it in GitHub Desktop.
<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