/// <summary> /// Private constructor. /// </summary> /// <param name="jwtString">The encoded JWT string.</param> /// <exception cref="FormatException">Thrown if the JWT format is invalid.</exception> private Jwt(string jwtString) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(jwtString), nameof(jwtString)); this.jwtString = jwtString; // Encoded JWTs are formatted as three URL encoded Base64URL encoded strings // separated by periods. The first two strings are actually JSON objects // and the last holds the signature bytes: // // 1. JWT header // 2. JWT payload (with the claims) // 3. JWT signature // // We're going to verify that these JSON objects exist and then parse // them as Newtonsoft [JObject] instances. var parts = jwtString.Split('.'); if (parts.Length != 3) { throw new FormatException("JWT must have three parts separated by periods."); } try { Header = JObject.Parse(Encoding.UTF8.GetString(NeonHelper.Base64UrlDecode(parts[0]))); Payload = JObject.Parse(Encoding.UTF8.GetString(NeonHelper.Base64UrlDecode(parts[1]))); Signature = NeonHelper.Base64UrlDecode(parts[2]); } catch (Exception e) { throw new FormatException("Cannot parse JWT JSON parts.", e); } }
public void Base64UrlEncoding() { // Verify that known values can be encoded with padding. Assert.Equal("", NeonHelper.Base64UrlEncode(Array.Empty <byte>(), retainPadding: true)); Assert.Equal("AA%3D%3D", NeonHelper.Base64UrlEncode(new byte[] { 0 }, retainPadding: true)); Assert.Equal("AAE%3D", NeonHelper.Base64UrlEncode(new byte[] { 0, 1 }, retainPadding: true)); Assert.Equal("AAEC", NeonHelper.Base64UrlEncode(new byte[] { 0, 1, 2 }, retainPadding: true)); Assert.Equal("AAECAw%3D%3D", NeonHelper.Base64UrlEncode(new byte[] { 0, 1, 2, 3 }, retainPadding: true)); Assert.Equal("AAECAwQ%3D", NeonHelper.Base64UrlEncode(new byte[] { 0, 1, 2, 3, 4 }, retainPadding: true)); // Verify that known values can be encoded without padding. Assert.Equal("", NeonHelper.Base64UrlEncode(Array.Empty <byte>())); Assert.Equal("AA", NeonHelper.Base64UrlEncode(new byte[] { 0 })); Assert.Equal("AAE", NeonHelper.Base64UrlEncode(new byte[] { 0, 1 })); Assert.Equal("AAEC", NeonHelper.Base64UrlEncode(new byte[] { 0, 1, 2 })); Assert.Equal("AAECAw", NeonHelper.Base64UrlEncode(new byte[] { 0, 1, 2, 3 })); Assert.Equal("AAECAwQ", NeonHelper.Base64UrlEncode(new byte[] { 0, 1, 2, 3, 4 })); // Verify that we can decode known values with padding. Assert.Equal(Array.Empty <byte>(), NeonHelper.Base64UrlDecode("")); Assert.Equal(new byte[] { 0 }, NeonHelper.Base64UrlDecode("AA%3D%3D")); Assert.Equal(new byte[] { 0, 1 }, NeonHelper.Base64UrlDecode("AAE%3D")); Assert.Equal(new byte[] { 0, 1, 2 }, NeonHelper.Base64UrlDecode("AAEC")); Assert.Equal(new byte[] { 0, 1, 2, 3 }, NeonHelper.Base64UrlDecode("AAECAw%3D%3D")); Assert.Equal(new byte[] { 0, 1, 2, 3, 4 }, NeonHelper.Base64UrlDecode("AAECAwQ%3D")); // Verify that we can decode known values without URL encoded padding. Assert.Equal(Array.Empty <byte>(), NeonHelper.Base64UrlDecode("")); Assert.Equal(new byte[] { 0 }, NeonHelper.Base64UrlDecode("AA")); Assert.Equal(new byte[] { 0, 1 }, NeonHelper.Base64UrlDecode("AAE")); Assert.Equal(new byte[] { 0, 1, 2 }, NeonHelper.Base64UrlDecode("AAEC")); Assert.Equal(new byte[] { 0, 1, 2, 3 }, NeonHelper.Base64UrlDecode("AAECAw")); Assert.Equal(new byte[] { 0, 1, 2, 3, 4 }, NeonHelper.Base64UrlDecode("AAECAwQ")); // Verify that we can decode known values with standard '=' padding. Assert.Equal(Array.Empty <byte>(), NeonHelper.Base64UrlDecode("")); Assert.Equal(new byte[] { 0 }, NeonHelper.Base64UrlDecode("AA==")); Assert.Equal(new byte[] { 0, 1 }, NeonHelper.Base64UrlDecode("AAE=")); Assert.Equal(new byte[] { 0, 1, 2 }, NeonHelper.Base64UrlDecode("AAEC")); Assert.Equal(new byte[] { 0, 1, 2, 3 }, NeonHelper.Base64UrlDecode("AAECAw==")); Assert.Equal(new byte[] { 0, 1, 2, 3, 4 }, NeonHelper.Base64UrlDecode("AAECAwQ=")); }