I am using the Keycloak Testcontainers library to run integration tests on my Net Core 7 project. I am also using the refit library to handle the api communication with the Keycloak Testcontainer.
Now all the api calls which don't have a body (getting realm settings, tokens etc) work well. The problem is when I try and send a request with a body such as when creating a new user and keycloak is returning a bad request error.
Refit is sending the request and when I am debugging I noticed that the content is being sent as pushatreamcontent and not a serialised json body.
Method: POST, RequestUri: 'http://localhost:53561/admin/realms/mNet/users', Version: 1.1, Content: System.Net.Http.PushStreamContent, Headers: { Content-Type: application/json; charset=utf-8 }
Any ideas?
Integration test
namespace mNet.Identity.IntegrationTests;
[Collection("Keycloak Test Collection")]
public class UserRegistrationSpecification
{
private readonly IKeycloakUserAdministrationService _sut;
private readonly IDateTimeProvider _dateTimeProvider =
Substitute.For<IDateTimeProvider>();
private readonly ILoggerAdapter<KeycloakUserAdministrationService> _logger =
Substitute.For<ILoggerAdapter<KeycloakUserAdministrationService>>();
private readonly ILoggerAdapter<AdminAuthorizationDelegatingHandler> _delegatingHandlerLogger =
Substitute.For<ILoggerAdapter<AdminAuthorizationDelegatingHandler>>();
public UserRegistrationSpecification(KeycloakTestContainer keycloakTestContainer)
{
var keycloakService = keycloakTestContainer.KeycloakService;
var keycloakTokenServiceRefitApi = RestService.For<IKeycloakTokenServiceRefitApi>(
hostUrl: keycloakService.TokenServiceUri!.AbsoluteUri);
var adminAuthorizationDelegatingHandler = new AdminAuthorizationDelegatingHandler(
keycloakService: keycloakService,
keycloakTokenServiceRefitApi: keycloakTokenServiceRefitApi,
logger: _delegatingHandlerLogger);
var httpClient = new HttpClient(adminAuthorizationDelegatingHandler)
{
BaseAddress = keycloakService.AccessManagementUri
};
var keycloakAccessManagementRefitApi = RestService.For<IKeycloakAccessManagementRefitApi>(
client: httpClient);
_sut = new KeycloakUserAdministrationService(
dateTimeProvider: _dateTimeProvider,
keycloakService: keycloakService,
keycloakAccessManagementRefitApi: keycloakAccessManagementRefitApi,
logger: _logger);
_dateTimeProvider.GetCurrentUtcTime().Returns(
CommonTestConstants.DateTime.CurrentTime);
}
[Fact]
public async Task Should_register_new_user_on_keycloak_when_valid_user_provided()
{
// Arrange
var user = UserTestUtilities.CreateUser.WithEmailOnly();
// Act
var result = await _sut.RegisterUserAsync(
user: user,
password: UserTestConstants.Setup.Password);
// Assert
result.IsSuccess.ShouldBeTrue();
}
}
Register user method
public async Task<Result<KeycloakId>> RegisterUserAsync(User user, string password,
CancellationToken cancellationToken = default)
{
try
{
var userRepresentationModel = user.MapToUserRepresentationKeycloakModel(_dateTimeProvider);
userRepresentationModel.Credentials = new CredentialRepresentation[]
{
new()
{
Value = password,
Temporary = false,
Type = KeycloakConstants.CredentialTypes.Password
}
};
var response = await _keycloakAccessManagementRefitApi.RegisterUserAsync(userRepresentationModel);
response.EnsureSuccessStatusCode();
_logger.LogDebug("Successfully registered user {UserId} through Keycloak API",
user.Email);
return ExtractKeycloakIdFromLocationHeader(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unable to register user {UserId} through Keycloak API",
user.Email);
return Result.Failure<KeycloakId>(KeycloakErrors.Api.RegisterUserFailure);
}
}
Authorisation handler (returns token successfully from Keycloak)
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
try
{
// Check client details set
var getAdminClientTokenRequestParametersResult = _keycloakService.GetAdminClientTokenRequestParameters();
if (getAdminClientTokenRequestParametersResult.IsFailure)
throw new Exception(getAdminClientTokenRequestParametersResult.Error.Message);
// Obtain the authorization token from Keycloak
var authorizationToken = await
_keycloakTokenServiceRefitApi.GetClientAccessToken(
getAdminClientTokenRequestParametersResult.Value);
_logger.LogDebug("Successfully obtained client access token from Keycloak");
// Attach the token to the request
request.Headers.Authorization = new AuthenticationHeaderValue(
JwtBearerDefaults.AuthenticationScheme,
authorizationToken.AccessToken);
// Send the request and obtain the response
var httpResponseMessage = await base.SendAsync(request, cancellationToken);
httpResponseMessage.EnsureSuccessStatusCode();
_logger.LogDebug("Successfully processed the following HTTP request on Keycloak API: {HttpRequest}",
request.ToString());
return httpResponseMessage;
}
catch (Exception ex)
{
// Log error and return bad request response upon exception
_logger.LogError(ex, "Unable to process the following HTTP request on Keycloak API: {HttpRequest}",
request.ToString());
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
Refit api interfaces
[Post("/users")]
Task<HttpResponseMessage> RegisterUserAsync([Body] UserRepresentation userRepresentationModel);