diff --git a/Libraries/Microsoft.Teams.Api/SignIn/StateVerifyQuery.cs b/Libraries/Microsoft.Teams.Api/SignIn/StateVerifyQuery.cs
index 66f877f9..c9cbf3c2 100644
--- a/Libraries/Microsoft.Teams.Api/SignIn/StateVerifyQuery.cs
+++ b/Libraries/Microsoft.Teams.Api/SignIn/StateVerifyQuery.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Teams.Api.SignIn;
@@ -13,9 +14,49 @@ public class StateVerifyQuery
///
/// The state string originally received when the
/// signin web flow is finished with a state posted back to client via tab SDK
- /// microsoftTeams.authentication.notifySuccess(state)
+ /// microsoftTeams.authentication.notifySuccess(state).
+ /// Can be either a string or a JSON object depending on the platform (Android/iOS may send objects).
+ /// When a JSON object is received, it is automatically serialized to a JSON string.
///
[JsonPropertyName("state")]
[JsonPropertyOrder(0)]
+ [JsonConverter(typeof(StringOrObjectConverter))]
public string? State { get; set; }
+
+ ///
+ /// Custom JSON converter that handles both string and object values for the State property.
+ /// When deserializing, if the value is a string, it returns the string value.
+ /// If the value is a JSON object (or any other type), it serializes it to a JSON string.
+ ///
+ private class StringOrObjectConverter : JsonConverter
+ {
+ public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.Null)
+ {
+ return null;
+ }
+
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ return reader.GetString();
+ }
+
+ // For any other token type (object, array, number, etc.), read as JsonElement and serialize
+ using var doc = JsonDocument.ParseValue(ref reader);
+ return JsonSerializer.Serialize(doc.RootElement);
+ }
+
+ public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
+ {
+ if (value == null)
+ {
+ writer.WriteNullValue();
+ }
+ else
+ {
+ writer.WriteStringValue(value);
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/Tests/Microsoft.Teams.Api.Tests/Activities/Invokes/SignIn/VerifyStateSignInActivityTests.cs b/Tests/Microsoft.Teams.Api.Tests/Activities/Invokes/SignIn/VerifyStateSignInActivityTests.cs
index 44d3a9a1..6164590d 100644
--- a/Tests/Microsoft.Teams.Api.Tests/Activities/Invokes/SignIn/VerifyStateSignInActivityTests.cs
+++ b/Tests/Microsoft.Teams.Api.Tests/Activities/Invokes/SignIn/VerifyStateSignInActivityTests.cs
@@ -147,4 +147,129 @@ public void setupSignInVerifyStateActivity_JsonDeserialize_Derived_Activity_Inte
Assert.NotNull(activity);
Assert.Equal(expected.ToString(), activity.ToString());
}
+
+ [Fact]
+ public void setupSignInVerifyStateActivity_JsonDeserialize_StateAsObject()
+ {
+ // Test JSON with state as an object (Android/iOS scenario)
+ var jsonWithObjectState = @"{
+ ""type"": ""invoke"",
+ ""channelId"": ""msteams"",
+ ""name"": ""signin/verifyState"",
+ ""value"": {
+ ""state"": {
+ ""token"": ""abc123"",
+ ""userId"": ""user123""
+ }
+ },
+ ""from"": {
+ ""id"": ""botId"",
+ ""aadObjectId"": ""aadObjectId"",
+ ""name"": ""User Name""
+ },
+ ""recipient"": {
+ ""id"": ""recipientId"",
+ ""name"": ""Recipient Name""
+ },
+ ""conversation"": {
+ ""id"": ""conversationId"",
+ ""conversationType"": ""groupChat""
+ }
+}";
+
+ var activity = JsonSerializer.Deserialize(jsonWithObjectState);
+
+ Assert.NotNull(activity);
+ Assert.NotNull(activity.Value);
+ Assert.NotNull(activity.Value.State);
+
+ // Verify the state was serialized to a JSON string
+ Assert.Contains("token", activity.Value.State);
+ Assert.Contains("abc123", activity.Value.State);
+ Assert.Contains("userId", activity.Value.State);
+ Assert.Contains("user123", activity.Value.State);
+ }
+
+ [Fact]
+ public void setupSignInVerifyStateActivity_JsonDeserialize_StateAsObject_ViaSignInActivity()
+ {
+ // Test JSON with state as an object through SignInActivity
+ var jsonWithObjectState = @"{
+ ""type"": ""invoke"",
+ ""channelId"": ""msteams"",
+ ""name"": ""signin/verifyState"",
+ ""value"": {
+ ""state"": {
+ ""sessionId"": ""session-456"",
+ ""redirectUrl"": ""https://example.com/callback""
+ }
+ },
+ ""from"": {
+ ""id"": ""botId"",
+ ""aadObjectId"": ""aadObjectId"",
+ ""name"": ""User Name""
+ },
+ ""recipient"": {
+ ""id"": ""recipientId"",
+ ""name"": ""Recipient Name""
+ },
+ ""conversation"": {
+ ""id"": ""conversationId"",
+ ""conversationType"": ""groupChat""
+ }
+}";
+
+ var activity = JsonSerializer.Deserialize(jsonWithObjectState);
+
+ Assert.NotNull(activity);
+ var verifyStateActivity = activity.ToVerifyState();
+ Assert.NotNull(verifyStateActivity);
+ Assert.NotNull(verifyStateActivity.Value.State);
+
+ // Verify the state was serialized to a JSON string
+ Assert.Contains("sessionId", verifyStateActivity.Value.State);
+ Assert.Contains("session-456", verifyStateActivity.Value.State);
+ }
+
+ [Fact]
+ public void setupSignInVerifyStateActivity_JsonDeserialize_StateAsObject_ViaActivity()
+ {
+ // Test JSON with state as an object through Activity
+ var jsonWithObjectState = @"{
+ ""type"": ""invoke"",
+ ""channelId"": ""msteams"",
+ ""name"": ""signin/verifyState"",
+ ""value"": {
+ ""state"": {
+ ""code"": ""auth-code-789""
+ }
+ },
+ ""from"": {
+ ""id"": ""botId"",
+ ""aadObjectId"": ""aadObjectId"",
+ ""name"": ""User Name""
+ },
+ ""recipient"": {
+ ""id"": ""recipientId"",
+ ""name"": ""Recipient Name""
+ },
+ ""conversation"": {
+ ""id"": ""conversationId"",
+ ""conversationType"": ""groupChat""
+ }
+}";
+
+ var activity = JsonSerializer.Deserialize(jsonWithObjectState);
+
+ Assert.NotNull(activity);
+ Assert.True(activity is InvokeActivity);
+ var invokeActivity = (InvokeActivity)activity;
+ Assert.True(invokeActivity is SignInActivity);
+ var signInActivity = (SignInActivity)invokeActivity;
+ var verifyStateActivity = signInActivity.ToVerifyState();
+
+ Assert.NotNull(verifyStateActivity.Value.State);
+ Assert.Contains("code", verifyStateActivity.Value.State);
+ Assert.Contains("auth-code-789", verifyStateActivity.Value.State);
+ }
}
\ No newline at end of file