[Reliability] KeycloakAdminService.GetRoleMembersAsync news a per-call HttpClient (leak + inconsistent with the rest of the class) #435

Closed
opened 2026-07-06 03:18:28 +00:00 by spikerj · 1 comment
Owner

Problem

SpikerSoft.Business/Services/KeycloakAdminService.cs is a primary-constructor class that already injects HttpClient httpClient, and every method uses it with a per-request HttpRequestMessage carrying the Authorization header (e.g. GetAdminTokenAsync, AssignRealmRoleAsync, the availability checks — all httpClient.SendAsync(...)).

GetRoleMembersAsync (line ~444) is the lone outlier:

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

This news + disposes an HttpClient on every call (socket/TIME_WAIT leak, same family as #411/#433/#434) and mutates DefaultRequestHeaders — the anti-pattern the rest of the class deliberately avoids.

Fix

Use the injected httpClient with a per-request HttpRequestMessage + Authorization header, matching every sibling method. No ctor change; URL construction unchanged.

Acceptance

  • No new HttpClient() in KeycloakAdminService; GetRoleMembersAsync uses the injected client.
  • Returns the same role members (unit-testable via the existing Mock<HttpMessageHandler> harness — the stub sits on the injected client, so the test only passes once the injected client is used).

Refs #411 (resource-leak availability bombs).

## Problem `SpikerSoft.Business/Services/KeycloakAdminService.cs` is a primary-constructor class that **already injects `HttpClient httpClient`**, and every method uses it with a per-request `HttpRequestMessage` carrying the `Authorization` header (e.g. `GetAdminTokenAsync`, `AssignRealmRoleAsync`, the availability checks — all `httpClient.SendAsync(...)`). `GetRoleMembersAsync` (line ~444) is the lone outlier: ```csharp using var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); ``` This news + disposes an `HttpClient` on every call (socket/`TIME_WAIT` leak, same family as #411/#433/#434) and mutates `DefaultRequestHeaders` — the anti-pattern the rest of the class deliberately avoids. ## Fix Use the injected `httpClient` with a per-request `HttpRequestMessage` + `Authorization` header, matching every sibling method. No ctor change; URL construction unchanged. ## Acceptance - No `new HttpClient()` in `KeycloakAdminService`; `GetRoleMembersAsync` uses the injected client. - Returns the same role members (unit-testable via the existing `Mock<HttpMessageHandler>` harness — the stub sits on the injected client, so the test only passes once the injected client is used). Refs #411 (resource-leak availability bombs).
Author
Owner

Resolved in spikersoft-backend PR #114 (merged to master). GetRoleMembersAsync now uses the injected HttpClient with a per-request Authorization header, matching every sibling method — no more per-call new HttpClient(). Guarded by KeycloakAdminServiceRoleMembersTests (stub on the injected client) + 21 existing Keycloak tests. This cleared the last non-benign new HttpClient() in the backend. Closing.

Resolved in spikersoft-backend PR #114 (merged to `master`). `GetRoleMembersAsync` now uses the injected `HttpClient` with a per-request `Authorization` header, matching every sibling method — no more per-call `new HttpClient()`. Guarded by `KeycloakAdminServiceRoleMembersTests` (stub on the injected client) + 21 existing Keycloak tests. This cleared the last non-benign `new HttpClient()` in the backend. Closing.
Sign in to join this conversation.