From cd4ff9f81c1e259c04260d93b330656efe83816c Mon Sep 17 00:00:00 2001 From: sps_campbellwray Date: Tue, 7 Jul 2026 15:41:04 -0400 Subject: [PATCH 1/2] Add request timeout option --- src/Keycloak.Net.Core/KeycloakClient.cs | 35 +++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Keycloak.Net.Core/KeycloakClient.cs b/src/Keycloak.Net.Core/KeycloakClient.cs index 37ec1a1b..bc4f4daf 100644 --- a/src/Keycloak.Net.Core/KeycloakClient.cs +++ b/src/Keycloak.Net.Core/KeycloakClient.cs @@ -54,16 +54,22 @@ public KeycloakClient(string url, public void SetSerializer(ISerializer serializer) => _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); - private IFlurlRequest GetBaseUrl(string authenticationRealm) => - new Url(_url).AppendPathSegment(_options.Prefix) - .WithSettings(settings => settings.JsonSerializer = _serializer) - .WithAuthentication(_getToken, - _url, - _options.AuthenticationRealm ?? authenticationRealm, - _userName, - _password, - _clientSecret, - _options); + private IFlurlRequest GetBaseUrl(string authenticationRealm) + { + var request = new Url(_url).AppendPathSegment(_options.Prefix) + .WithSettings(settings => settings.JsonSerializer = _serializer) + .WithAuthentication(_getToken, + _url, + _options.AuthenticationRealm ?? authenticationRealm, + _userName, + _password, + _clientSecret, + _options); + + return _options.TimeOut.HasValue + ? request.WithTimeout(_options.TimeOut.Value) + : request; + } internal record CountDto(int Count); } @@ -83,14 +89,21 @@ public class KeycloakOptions /// public IKeycloakAccessTokenCache? AccessTokenCache { get; } + /// + /// Specify a timeout for Keycloak requests. + /// + public TimeSpan? TimeOut { get; } + public KeycloakOptions(string prefix = "", string adminClientId = "admin-cli", string? authenticationRealm = null, - IKeycloakAccessTokenCache? accessTokenCache = null) + IKeycloakAccessTokenCache? accessTokenCache = null, + TimeSpan? timeOut = null) { Prefix = prefix.TrimStart('/').TrimEnd('/'); AdminClientId = adminClientId; AuthenticationRealm = authenticationRealm; AccessTokenCache = accessTokenCache; + TimeOut = timeOut; } } From 54124679f01c646ee72b11acaa06b2ffa4b60e18 Mon Sep 17 00:00:00 2001 From: sps_campbellwray Date: Tue, 7 Jul 2026 16:55:38 -0400 Subject: [PATCH 2/2] Address timeout review fixes --- .../Extensions/FlurlRequestExtensions.cs | 56 +++++++++++-------- src/Keycloak.Net.Core/KeycloakClient.cs | 17 +++--- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/Keycloak.Net.Core/Common/Extensions/FlurlRequestExtensions.cs b/src/Keycloak.Net.Core/Common/Extensions/FlurlRequestExtensions.cs index bf41c290..82a9e6c8 100644 --- a/src/Keycloak.Net.Core/Common/Extensions/FlurlRequestExtensions.cs +++ b/src/Keycloak.Net.Core/Common/Extensions/FlurlRequestExtensions.cs @@ -36,17 +36,23 @@ private static async Task GetAccessTokenAsync(string url, } } - var result = await url.AppendPathSegment($"{options.Prefix}/realms/{realm}/protocol/openid-connect/token") - .WithHeader("Accept", "application/json") - .PostUrlEncodedAsync(new List> - { - new("grant_type", "password"), - new("username", userName), - new("password", password), - new("client_id", options.AdminClientId) - }) - .ReceiveJson() - .ConfigureAwait(false); + var request = url.AppendPathSegment($"{options.Prefix}/realms/{realm}/protocol/openid-connect/token") + .WithHeader("Accept", "application/json"); + + if (options.Timeout.HasValue) + { + request = request.WithTimeout(options.Timeout.Value); + } + + var result = await request.PostUrlEncodedAsync(new List> + { + new("grant_type", "password"), + new("username", userName), + new("password", password), + new("client_id", options.AdminClientId) + }) + .ReceiveJson() + .ConfigureAwait(false); if (canCache) { @@ -88,16 +94,22 @@ private static async Task GetAccessTokenAsync(string url, } } - var result = await url.AppendPathSegment($"{options.Prefix}/realms/{realm}/protocol/openid-connect/token") - .WithHeader("Content-Type", "application/x-www-form-urlencoded") - .PostUrlEncodedAsync(new List> - { - new("grant_type", "client_credentials"), - new("client_secret", clientSecret), - new("client_id", options.AdminClientId) - }) - .ReceiveJson() - .ConfigureAwait(false); + var request = url.AppendPathSegment($"{options.Prefix}/realms/{realm}/protocol/openid-connect/token") + .WithHeader("Content-Type", "application/x-www-form-urlencoded"); + + if (options.Timeout.HasValue) + { + request = request.WithTimeout(options.Timeout.Value); + } + + var result = await request.PostUrlEncodedAsync(new List> + { + new("grant_type", "client_credentials"), + new("client_secret", clientSecret), + new("client_id", options.AdminClientId) + }) + .ReceiveJson() + .ConfigureAwait(false); if (canCache) { @@ -158,4 +170,4 @@ public static IFlurlRequest WithForwardedHttpHeaders(this IFlurlRequest request, } internal record AccessTokenDto([property: JsonPropertyName("access_token")] string AccessToken); -} \ No newline at end of file +} diff --git a/src/Keycloak.Net.Core/KeycloakClient.cs b/src/Keycloak.Net.Core/KeycloakClient.cs index bc4f4daf..d822613b 100644 --- a/src/Keycloak.Net.Core/KeycloakClient.cs +++ b/src/Keycloak.Net.Core/KeycloakClient.cs @@ -66,9 +66,12 @@ private IFlurlRequest GetBaseUrl(string authenticationRealm) _clientSecret, _options); - return _options.TimeOut.HasValue - ? request.WithTimeout(_options.TimeOut.Value) - : request; + if (_options.Timeout.HasValue) + { + request = request.WithTimeout(_options.Timeout.Value); + } + + return request; } internal record CountDto(int Count); @@ -90,20 +93,20 @@ public class KeycloakOptions public IKeycloakAccessTokenCache? AccessTokenCache { get; } /// - /// Specify a timeout for Keycloak requests. + /// Specify the maximum time to wait for HTTP requests. /// - public TimeSpan? TimeOut { get; } + public TimeSpan? Timeout { get; } public KeycloakOptions(string prefix = "", string adminClientId = "admin-cli", string? authenticationRealm = null, IKeycloakAccessTokenCache? accessTokenCache = null, - TimeSpan? timeOut = null) + TimeSpan? timeout = null) { Prefix = prefix.TrimStart('/').TrimEnd('/'); AdminClientId = adminClientId; AuthenticationRealm = authenticationRealm; AccessTokenCache = accessTokenCache; - TimeOut = timeOut; + Timeout = timeout; } }