diff --git a/Assets/Sui-Unity-SDK/Code/Sui.ZKLogin/JwtDecoder.cs b/Assets/Sui-Unity-SDK/Code/Sui.ZKLogin/JwtDecoder.cs
index 6bbe26b..eebc0c5 100644
--- a/Assets/Sui-Unity-SDK/Code/Sui.ZKLogin/JwtDecoder.cs
+++ b/Assets/Sui-Unity-SDK/Code/Sui.ZKLogin/JwtDecoder.cs
@@ -4,6 +4,7 @@ namespace OpenDive.Utils.Jwt
using System.Text;
using UnityEngine;
using Newtonsoft.Json;
+ using System.Collections.Generic;
///
/// A class to decode JWT tokens.
@@ -56,6 +57,58 @@ public static JWT DecodeJWT(string token)
}
}
+ ///
+ /// Utility class to decode non-required claims.
+ ///
+ /// The JWT token string
+ /// Parsed claims
+ private static void ParseCustomClaims(string json, Dictionary claims)
+ {
+ // Simple JSON parsing for claims
+ // Remove the first and last curly braces and split by commas
+ json = json.Trim('{', '}');
+ string[] pairs = json.Split(',');
+
+ foreach (string pair in pairs)
+ {
+ try
+ {
+ string[] keyValue = pair.Split(':');
+ if (keyValue.Length == 2)
+ {
+ string key = keyValue[0].Trim('"', ' ');
+ string value = keyValue[1].Trim('"', ' ');
+
+ // Skip standard claims
+ if (!IsStandardClaim(key))
+ {
+ claims[key] = value;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.LogWarning($"Failed to parse claim pair: {pair}. Error: {ex.Message}");
+ }
+ }
+ }
+
+ ///
+ /// Utility class to check against standard claims.
+ ///
+ ///
+ ///
+ private static bool IsStandardClaim(string claimName)
+ {
+ return claimName == "iss" ||
+ claimName == "sub" ||
+ claimName == "aud" ||
+ claimName == "exp" ||
+ claimName == "nbf" ||
+ claimName == "iat" ||
+ claimName == "jti";
+ }
+
///
/// Decodes a Base64 URL-encoded string.
///
@@ -80,8 +133,46 @@ private static string Base64UrlDecode(string base64Url)
///
public class JWT
{
+ ///
+ /// The JOSE (JSON Object Signing and Encryption) Header is comprised
+ /// of a set of Header Parameters that typically consist of a name/value pair:
+ /// the hashing algorithm being used (e.g., HMAC SHA256 or RSA) and the type of the JWT.
+ ///
public JWTHeader Header { get; set; }
+ ///
+ /// JWS payload (set of claims): contains verifiable security statements.
+ /// These are statements about an entity (typically, the user) and additional data.
+ /// There are three types of claims: registered, public, and private claims.
+ ///
+ /// Registered claims: These are a set of predefined claims which are
+ /// not mandatory but recommended, to provide a set of useful, interoperable claims.
+ /// Some of them are: iss (issuer), exp (expiration time), sub (subject), aud (audience), and others.
+ ///
+ /// Public claims: These can be defined at will by those using JWTs.
+ /// But to avoid collisions they should be defined in the IANA JSON Web Token Registry
+ /// or be defined as a URI that contains a collision resistant namespace.
+ ///
+ /// Private claims: These are the custom claims created to share
+ /// information between parties that agree on using them and are
+ /// neither registered or public claims.
+ ///
public JWTPayload Payload { get; set; }
+ ///
+ /// The signature is used to verify that the sender of the JWT is who
+ /// it says it is and to ensure that the message wasn't changed along the way.
+ /// To create the signature, the Base64-encoded header and payload are taken,
+ /// along with a secret, and signed with the algorithm specified in the header.
+ ///
+ /// When you use a JWT, you must check its signature before storing and using it.
+ ///
+ /// For example, if you are creating a signature for a token using the
+ /// HMAC SHA256 algorithm, you would do the following:
+ /// HMACSHA256(
+ /// base64UrlEncode(header) + "." +
+ /// base64UrlEncode(payload),
+ /// secret)
+ ///
+ ///
public string Signature { get; set; }
}
@@ -90,8 +181,9 @@ public class JWT
///
public class JWTHeader
{
- public string alg { get; set; } // Algorithm
- public string typ { get; set; } // Token type
+ public string alg { get; set; } // * Algorithm. Required for ZK Login.
+ public string typ { get; set; } // * Token type. Required for ZK Login.
+ public string kid { get; set; } // * The kid value indicates what key was used to sign the JWT. Required for ZK Login.
}
///
@@ -99,16 +191,22 @@ public class JWTHeader
///
public class JWTPayload
{
- public string Iss { get; set; } // Issuer
- public string Sub { get; set; } // Subject
- public string Aud { get; set; } // Audience
+ // <> Registered claims >
+ public string Iss { get; set; } // * Issuer of the JWT. Required for ZK Login.
+ public string Sub { get; set; } // * Subject of the JWT (the user). Required for ZK Login.
+ public string Aud { get; set; } // Recipient for which the JWT is intended
+ public long? Exp { get; set; } // Time after which the JWT expires
+ public long? Nbf { get; set; } // Time before which the JWT must not be accepted for processing (Unix timestamp)
+ public long? Iat { get; set; } // Issued at .. Time at which the JWT was issued; can be used to determine age of the JWT (Unix timestamp)
+ public string Jti { get; set; } // JWT ID (unique identifier for the token). Can be used to prevent the JWT from being replayed (allows a token to be used only once)
+
+ // <> Public claims >
public long? Auth_time { get; set; } // Authentication time (Unix timestamp)
- public long? Iat { get; set; } // Issued at (Unix timestamp)
- public long? Exp { get; set; } // Expiration time (Unix timestamp)
- public string Email { get; set; } // Email address
- public string Name { get; set; } // Name
- public string Jti { get; set; } // JWT ID (unique identifier for the token)
+ public string Nonce { get; set; } // * Required for ZK Login. Used instead of `iat` and `exp`.
+ // <> Custom claims >
+ public string Email { get; set; } // Email address
+ public string Name { get; set; } // Name
// Add additional properties as needed based on your JWT structure.
}
diff --git a/Assets/Sui-Unity-SDK/Tests/JwtDecoderTest.cs b/Assets/Sui-Unity-SDK/Tests/JwtDecoderTest.cs
new file mode 100644
index 0000000..42a17fb
--- /dev/null
+++ b/Assets/Sui-Unity-SDK/Tests/JwtDecoderTest.cs
@@ -0,0 +1,37 @@
+using NUnit.Framework;
+using OpenDive.Utils.Jwt;
+
+namespace Sui.Tests
+{
+ [TestFixture]
+ public class JwDecoderTest
+ {
+ string jwt_sample_1
+ = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODUxNDA5ODQsImlhdCI6MTQ4NTEzNzM4NCwiaXNzIjoiYWNtZS5jb20iLCJzdWIiOiIyOWFjMGMxOC0wYjRhLTQyY2YtODJmYy0wM2Q1NzAzMThhMWQiLCJhcHBsaWNhdGlvbklkIjoiNzkxMDM3MzQtOTdhYi00ZDFhLWFmMzctZTAwNmQwNWQyOTUyIiwicm9sZXMiOltdfQ.Mp0Pcwsz5VECK11Kf2ZZNF_SMKu5CgBeLN9ZOP04kZo\n";
+ [Test]
+ public void DecodeJwtTest()
+ {
+ JWT jwtDecoded = JWTDecoder.DecodeJWT(jwt_sample_1);
+
+ JWTHeader header = jwtDecoded.Header;
+ string algo_expected = "HS256";
+ string typ_expected = "JWT";
+ Assert.AreEqual(algo_expected, header.alg);
+ Assert.AreEqual(typ_expected, header.typ);
+
+ JWTPayload payload = jwtDecoded.Payload;
+ long exp_expected = 1485140984; // (long)payload.Exp;
+ long iat_expected = 1485137384;
+ string iss_expected = "acme.com";
+ string sub_expected = "29ac0c18-0b4a-42cf-82fc-03d570318a1d";
+ string application_id = "79103734-97ab-4d1a-af37-e006d05d2952";
+ string[] roles_expected = { };
+ Assert.AreEqual(exp_expected, payload.Exp);
+ Assert.AreEqual(iat_expected, payload.Iat);
+ Assert.AreEqual(iss_expected, payload.Iss);
+ Assert.AreEqual(sub_expected, payload.Sub);
+ //Assert.AreEqual(application_id, payload...)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Assets/Sui-Unity-SDK/Tests/JwtDecoderTest.cs.meta b/Assets/Sui-Unity-SDK/Tests/JwtDecoderTest.cs.meta
new file mode 100644
index 0000000..a7ea96b
--- /dev/null
+++ b/Assets/Sui-Unity-SDK/Tests/JwtDecoderTest.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 75c6c2d8312c94a0cbd1ca402444deb0
\ No newline at end of file
diff --git a/Assets/Sui-Unity-SDK/Tests/JwtUtilsTests.cs b/Assets/Sui-Unity-SDK/Tests/JwtUtilsTests.cs
index 1a5ba85..5f02d3e 100644
--- a/Assets/Sui-Unity-SDK/Tests/JwtUtilsTests.cs
+++ b/Assets/Sui-Unity-SDK/Tests/JwtUtilsTests.cs
@@ -2,110 +2,113 @@
using NUnit.Framework;
using Sui.ZKLogin;
-[TestFixture]
-public class JwtUtilsTests
+namespace Sui.Tests
{
- [Test]
- public void ExtractClaimValue_ValidClaim_ReturnsCorrectValue()
+ [TestFixture]
+ public class JwtUtilsTests
{
- // Arrange
- var claim = new Claim
+ [Test]
+ public void ExtractClaimValue_ValidClaim_ReturnsCorrectValue()
{
- value = "eyJuYW1lIjoiSm9obiJ9", // Base64URL encoded '{"name":"John"}'
- indexMod4 = 0
- };
-
- // Act
- string result = JwtUtils.ExtractClaimValue(claim, "name");
-
- // Assert
- Assert.AreEqual("John", result);
- }
-
- [Test]
- public void ExtractClaimValue_InvalidIndex_ThrowsException()
- {
- var claim = new Claim
+ // Arrange
+ var claim = new Claim
+ {
+ value = "eyJuYW1lIjoiSm9obiJ9", // Base64URL encoded '{"name":"John"}'
+ indexMod4 = 0
+ };
+
+ // Act
+ string result = JwtUtils.ExtractClaimValue(claim, "name");
+
+ // Assert
+ Assert.AreEqual("John", result);
+ }
+
+ [Test]
+ public void ExtractClaimValue_InvalidIndex_ThrowsException()
{
- value = "eyJuYW1lIjoiSm9obiJ9",
- indexMod4 = 3 // Invalid index
- };
-
- Assert.Throws(() =>
- JwtUtils.ExtractClaimValue(claim, "name"));
- }
-
- [Test]
- public void ExtractClaimValue_WrongClaimName_ThrowsException()
- {
- var claim = new Claim
+ var claim = new Claim
+ {
+ value = "eyJuYW1lIjoiSm9obiJ9",
+ indexMod4 = 3 // Invalid index
+ };
+
+ Assert.Throws(() =>
+ JwtUtils.ExtractClaimValue(claim, "name"));
+ }
+
+ [Test]
+ public void ExtractClaimValue_WrongClaimName_ThrowsException()
{
- value = "eyJuYW1lIjoiSm9obiJ9",
- indexMod4 = 0
- };
-
- Assert.Throws(() =>
- JwtUtils.ExtractClaimValue(claim, "wrongName"));
- }
-
- [Test]
- public void ExtractClaimValue_ComplexObject_DeserializesCorrectly()
- {
- var claim = new Claim
+ var claim = new Claim
+ {
+ value = "eyJuYW1lIjoiSm9obiJ9",
+ indexMod4 = 0
+ };
+
+ Assert.Throws(() =>
+ JwtUtils.ExtractClaimValue(claim, "wrongName"));
+ }
+
+ [Test]
+ public void ExtractClaimValue_ComplexObject_DeserializesCorrectly()
{
- value = "eyJ1c2VyIjp7Im5hbWUiOiJKb2huIiwiYWdlIjozMH19",
- indexMod4 = 0
- };
+ var claim = new Claim
+ {
+ value = "eyJ1c2VyIjp7Im5hbWUiOiJKb2huIiwiYWdlIjozMH19",
+ indexMod4 = 0
+ };
- var result = JwtUtils.ExtractClaimValue(claim, "user");
+ var result = JwtUtils.ExtractClaimValue(claim, "user");
- Assert.AreEqual("John", result.name);
- Assert.AreEqual(30, result.age);
- }
+ Assert.AreEqual("John", result.name);
+ Assert.AreEqual(30, result.age);
+ }
- [Test]
- public void ExtractClaimValue_ShortInput_ThrowsException()
- {
- var claim = new Claim
+ [Test]
+ public void ExtractClaimValue_ShortInput_ThrowsException()
{
- value = "a", // Too short
- indexMod4 = 0
- };
-
- Assert.Throws(() =>
- JwtUtils.ExtractClaimValue(claim, "test"));
- }
-
- [Test]
- public void ExtractClaimValue_InvalidBase64Url_ThrowsException()
- {
- var claim = new Claim
+ var claim = new Claim
+ {
+ value = "a", // Too short
+ indexMod4 = 0
+ };
+
+ Assert.Throws(() =>
+ JwtUtils.ExtractClaimValue(claim, "test"));
+ }
+
+ [Test]
+ public void ExtractClaimValue_InvalidBase64Url_ThrowsException()
{
- value = "!@#$%^", // Invalid characters
- indexMod4 = 0
- };
-
- Assert.Throws(() =>
- JwtUtils.ExtractClaimValue(claim, "test"));
+ var claim = new Claim
+ {
+ value = "!@#$%^", // Invalid characters
+ indexMod4 = 0
+ };
+
+ Assert.Throws(() =>
+ JwtUtils.ExtractClaimValue(claim, "test"));
+ }
+
+ [Test]
+ public void ExtractClaimValue_MultipleKeysInJson_ThrowsException()
+ {
+ var claim = new Claim
+ {
+ value = "eyJrZXkxIjoidmFsdWUxIiwia2V5MiI6InZhbHVlMiJ9",
+ indexMod4 = 0
+ };
+
+ Assert.Throws(() =>
+ JwtUtils.ExtractClaimValue(claim, "key1"));
+ }
}
- [Test]
- public void ExtractClaimValue_MultipleKeysInJson_ThrowsException()
+ [Serializable]
+ public class UserData
{
- var claim = new Claim
- {
- value = "eyJrZXkxIjoidmFsdWUxIiwia2V5MiI6InZhbHVlMiJ9",
- indexMod4 = 0
- };
-
- Assert.Throws(() =>
- JwtUtils.ExtractClaimValue(claim, "key1"));
+ public string name;
+ public int age;
}
-}
-
-[Serializable]
-public class UserData
-{
- public string name;
- public int age;
}
\ No newline at end of file