Repository: floci-io/floci (2026-04-13T23:10:56Z)

Issue:
[BUG] Cognito IDP ListUserPoolClients Response doesn't match spec
## Service

Cognito IDP

## AWS API Action

ListUserPoolClients

## Expected behavior

https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ListUserPoolClients.html
Response should include only ClientId, ClientName and UserPoolId

## Actual behavior

It includes all properties in the response, including ClientSecret, GenerateSecret, AllowedOAuthFlowsUserPoolClient, AllowedOAuthFlows, AllowedOAuthScopes, CreationDate and LastModifiedDate

## Reproduction

```bash
# Minimal AWS CLI or SDK snippet that triggers the issue
aws  --endpoint-url http://localhost:4566 list-user-pool-clients --user-pool-id <user-pool-id>
```


## Environment

- Floci version / image tag: latest
- Java SDK version (if applicable): --
- How you're running Floci (Docker / native / `mvn quarkus:dev`): --


Discussion:
Hi @danielabeledo, thanks for taking the time to report this! 

We've seen it and will take a look. 

The Floci team




Patch:
diff --git a/src/main/java/io/github/hectorvent/floci/services/cognito/CognitoJsonHandler.java b/src/main/java/io/github/hectorvent/floci/services/cognito/CognitoJsonHandler.java
index fcc4c74a..deceb446 100644
--- a/src/main/java/io/github/hectorvent/floci/services/cognito/CognitoJsonHandler.java
+++ b/src/main/java/io/github/hectorvent/floci/services/cognito/CognitoJsonHandler.java
@@ -152,7 +152,7 @@ private Response handleListUserPoolClients(JsonNode request) {
         List<UserPoolClient> clients = service.listUserPoolClients(request.path("UserPoolId").asText());
         ObjectNode response = objectMapper.createObjectNode();
         ArrayNode items = response.putArray("UserPoolClients");
-        clients.forEach(c -> items.add(clientToNode(c)));
+        clients.forEach(c -> items.add(clientToDescriptionNode(c)));
         return Response.ok(response).build();
     }
 
@@ -465,6 +465,14 @@ private ObjectNode userPoolToFullNode(UserPool p) {
         return node;
     }
 
+    private ObjectNode clientToDescriptionNode(UserPoolClient c) {
+        ObjectNode node = objectMapper.createObjectNode();
+        node.put("ClientId", c.getClientId());
+        node.put("ClientName", c.getClientName());
+        node.put("UserPoolId", c.getUserPoolId());
+        return node;
+    }
+
     private ObjectNode clientToNode(UserPoolClient c) {
         ObjectNode node = objectMapper.createObjectNode();
         node.put("ClientId", c.getClientId());


Test patch:
diff --git a/compatibility-tests/sdk-test-awscli/test/cognito.bats b/compatibility-tests/sdk-test-awscli/test/cognito.bats
index c9159d6b..ba0eb942 100644
--- a/compatibility-tests/sdk-test-awscli/test/cognito.bats
+++ b/compatibility-tests/sdk-test-awscli/test/cognito.bats
@@ -32,6 +32,33 @@ teardown() {
     [ -n "$CLIENT_ID" ]
 }
 
+@test "Cognito: list user pool clients returns only description fields" {
+    out=$(aws_cmd cognito-idp create-user-pool --pool-name "bats-test-pool-$(unique_name)")
+    POOL_ID=$(json_get "$out" '.UserPool.Id')
+
+    aws_cmd cognito-idp create-user-pool-client \
+        --user-pool-id "$POOL_ID" \
+        --client-name "bats-list-client" \
+        --generate-secret >/dev/null
+
+    run aws_cmd cognito-idp list-user-pool-clients --user-pool-id "$POOL_ID"
+    assert_success
+
+    # Should have the required fields
+    client_id=$(echo "$output" | jq -r '.UserPoolClients[0].ClientId')
+    [ -n "$client_id" ]
+    client_name=$(echo "$output" | jq -r '.UserPoolClients[0].ClientName')
+    [ "$client_name" = "bats-list-client" ]
+
+    # Must NOT have fields that belong to the full UserPoolClient type
+    has_secret=$(echo "$output" | jq 'any(.UserPoolClients[]; has("ClientSecret"))')
+    [ "$has_secret" = "false" ]
+    has_generate=$(echo "$output" | jq 'any(.UserPoolClients[]; has("GenerateSecret"))')
+    [ "$has_generate" = "false" ]
+    has_flows=$(echo "$output" | jq 'any(.UserPoolClients[]; has("AllowedOAuthFlows"))')
+    [ "$has_flows" = "false" ]
+}
+
 @test "Cognito: admin create user" {
     out=$(aws_cmd cognito-idp create-user-pool --pool-name "bats-test-pool-$(unique_name)")
     POOL_ID=$(json_get "$out" '.UserPool.Id')
diff --git a/src/test/java/io/github/hectorvent/floci/services/cognito/CognitoIntegrationTest.java b/src/test/java/io/github/hectorvent/floci/services/cognito/CognitoIntegrationTest.java
index b7bc724f..c18f0ccb 100644
--- a/src/test/java/io/github/hectorvent/floci/services/cognito/CognitoIntegrationTest.java
+++ b/src/test/java/io/github/hectorvent/floci/services/cognito/CognitoIntegrationTest.java
@@ -358,6 +358,70 @@ void idTokenDoesNotContainClientId() throws Exception {
                 "IdToken should not contain client_id");
     }
 
+    // ── Issue #416: ListUserPoolClients response matches spec ──────────
+
+    @Test
+    @Order(35)
+    void listUserPoolClientsReturnsOnlyDescriptionFields() throws Exception {
+        // Create a client with a secret to ensure extra fields exist
+        JsonNode secretClient = cognitoJson("CreateUserPoolClient", """
+                {
+                  "UserPoolId": "%s",
+                  "ClientName": "secret-client",
+                  "GenerateSecret": true,
+                  "AllowedOAuthFlows": ["code"],
+                  "AllowedOAuthScopes": ["openid"]
+                }
+                """.formatted(poolId));
+        String secretClientId = secretClient.path("UserPoolClient").path("ClientId").asText();
+        assertNotNull(secretClient.path("UserPoolClient").path("ClientSecret").asText(null),
+                "Created client should have a ClientSecret");
+
+        // List clients and verify only description fields are returned
+        JsonNode listResp = cognitoJson("ListUserPoolClients", """
+                { "UserPoolId": "%s" }
+                """.formatted(poolId));
+
+        assertTrue(listResp.path("UserPoolClients").size() >= 2,
+                "Should list at least 2 clients");
+
+        for (JsonNode client : listResp.path("UserPoolClients")) {
+            // Required fields
+            assertTrue(client.has("ClientId"), "Must have ClientId");
+            assertTrue(client.has("ClientName"), "Must have ClientName");
+            assertTrue(client.has("UserPoolId"), "Must have UserPoolId");
+
+            // Fields that must NOT appear per AWS spec (UserPoolClientDescription)
+            assertTrue(client.path("ClientSecret").isMissingNode(),
+                    "ListUserPoolClients must not include ClientSecret");
+            assertTrue(client.path("GenerateSecret").isMissingNode(),
+                    "ListUserPoolClients must not include GenerateSecret");
+            assertTrue(client.path("AllowedOAuthFlows").isMissingNode(),
+                    "ListUserPoolClients must not include AllowedOAuthFlows");
+            assertTrue(client.path("AllowedOAuthScopes").isMissingNode(),
+                    "ListUserPoolClients must not include AllowedOAuthScopes");
+            assertTrue(client.path("AllowedOAuthFlowsUserPoolClient").isMissingNode(),
+                    "ListUserPoolClients must not include AllowedOAuthFlowsUserPoolClient");
+            assertTrue(client.path("CreationDate").isMissingNode(),
+                    "ListUserPoolClients must not include CreationDate");
+            assertTrue(client.path("LastModifiedDate").isMissingNode(),
+                    "ListUserPoolClients must not include LastModifiedDate");
+        }
+
+        // Verify DescribeUserPoolClient still returns full details
+        JsonNode describeResp = cognitoJson("DescribeUserPoolClient", """
+                {
+                  "UserPoolId": "%s",
+                  "ClientId": "%s"
+                }
+                """.formatted(poolId, secretClientId));
+        JsonNode fullClient = describeResp.path("UserPoolClient");
+        assertNotNull(fullClient.path("ClientSecret").asText(null),
+                "DescribeUserPoolClient must include ClientSecret");
+        assertTrue(fullClient.has("GenerateSecret"),
+                "DescribeUserPoolClient must include GenerateSecret");
+    }
+
     // ── Issue #229: Password verification ──────────────────────────────
 
     @Test