diff --git a/Lombiq.Tests.UI/Extensions/HttpClientUITestContextExtensions.cs b/Lombiq.Tests.UI/Extensions/HttpClientUITestContextExtensions.cs
new file mode 100644
index 000000000..29cc79416
--- /dev/null
+++ b/Lombiq.Tests.UI/Extensions/HttpClientUITestContextExtensions.cs
@@ -0,0 +1,184 @@
+using Lombiq.Tests.UI.Services;
+using Microsoft.SqlServer.Management.Dmf;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+
+namespace Lombiq.Tests.UI.Extensions;
+
+[SuppressMessage(
+ "Security",
+ "SCS0004: Certificate Validation has been disabled.",
+ Justification = "Certificate validation is unnecessary for UI testing.")]
+[SuppressMessage(
+ "Reliability",
+ "CA2000:Dispose objects before losing scope",
+ Justification = "Disposed by the HttpClient.")]
+public static class HttpClientUITestContextExtensions
+{
+ ///
+ /// Creates a new and authorizes it with a Bearer token that is created based on the provided
+ /// parameters.
+ ///
+ public static async Task CreateAndAuthorizeClientAsync(
+ this UITestContext context,
+ string grantType = "client_credentials",
+ string clientId = "UITest",
+ string clientSecret = "Password",
+ string userName = null,
+ string password = null)
+ {
+ var handler = new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
+ CheckCertificateRevocationList = true,
+ };
+
+ var client = new HttpClient(handler)
+ {
+ BaseAddress = context.Scope.BaseUri,
+ };
+
+ var parameters = new List>
+ {
+ new("grant_type", grantType),
+ new("client_id", clientId),
+ new("client_secret", clientSecret),
+ };
+
+ if (string.Equals(grantType, "password", StringComparison.OrdinalIgnoreCase))
+ {
+ parameters.Add(new KeyValuePair("username", userName));
+ parameters.Add(new KeyValuePair("password", password));
+ }
+
+ using var requestBody = new FormUrlEncodedContent(parameters);
+
+ var tokenUrl = context.Scope.BaseUri.AbsoluteUri + "connect/token";
+ var tokenResponse = await client.PostAsync(tokenUrl, requestBody);
+
+ if (!tokenResponse.IsSuccessStatusCode)
+ {
+ throw new InvalidOperandException(
+ $"Failed to get token for user in {nameof(CreateAndAuthorizeClientAsync)}. TokenResponse: {tokenResponse}");
+ }
+
+ var responseContent = await tokenResponse.Content.ReadAsStringAsync();
+ var token = JsonNode.Parse(responseContent)?["access_token"]?.ToString();
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
+
+ return client;
+ }
+
+ ///
+ /// Issues a GET request to the given using the provided .
+ ///
+ /// The response's as a string.
+ public static async Task GetAndReadResponseContentAsync(
+ this UITestContext context,
+ HttpClient client,
+ string requestUri)
+ {
+ var response = await client.GetAsync(requestUri);
+ return await response.Content.ReadAsStringAsync();
+ }
+
+ ///
+ /// Issues a GET request to the given using the provided then
+ /// deserializes the response content to the given .
+ ///
+ /// The response's as a string.
+ public static async Task GetAndReadResponseContentAsync(
+ this UITestContext context,
+ HttpClient client,
+ string requestUri)
+ where TObject : class
+ {
+ var content = await GetAndReadResponseContentAsync(context, client, requestUri);
+ var parsed = JToken.Parse(content);
+ return parsed.ToObject();
+ }
+
+ ///
+ /// Issues a POST request to the given using the provided and
+ /// .
+ ///
+ /// The response's as a string.
+ public static async Task PostAndReadResponseContentAsync(
+ this UITestContext context,
+ HttpClient client,
+ string requestUri,
+ string json) =>
+ await (await PostAndGetResponseAsync(client, requestUri, json)).Content.ReadAsStringAsync();
+
+ ///
+ /// Issues a POST request to the given using the provided then
+ /// deserializes the response content to the given .
+ ///
+ /// The deserialized object.
+ public static async Task PostAndReadResponseContentAsync(
+ this UITestContext context,
+ HttpClient client,
+ string requestUri,
+ string json)
+ where TObject : class
+ {
+ var content = await (await PostAndGetResponseAsync(client, requestUri, json)).Content.ReadAsStringAsync();
+ var parsed = JToken.Parse(content);
+
+ return parsed.ToObject();
+ }
+
+ ///
+ /// Issues a POST request to the given using the provided
+ /// which will be serialized as json.
+ ///
+ /// The response's as a string.
+ public static Task PostAndReadResponseContentAsync(
+ this UITestContext context,
+ HttpClient client,
+ object objectToSerialize,
+ string requestUri)
+ {
+ var json = JsonSerializer.Serialize(objectToSerialize);
+ return PostAndReadResponseContentAsync(context, client, requestUri, json);
+ }
+
+ ///
+ /// Issues a POST request to the given using the provided
+ /// which will be serialized as json.
+ ///
+ /// The .
+ public static Task PostAndGetResponseAsync(
+ this UITestContext context,
+ HttpClient client,
+ object objectToSerialize,
+ string requestUri)
+ {
+ var json = JsonSerializer.Serialize(objectToSerialize);
+ return PostAndGetResponseAsync(client, requestUri, json);
+ }
+
+ ///
+ /// Issues a POST request to the given using the provided .
+ ///
+ /// The .
+ public static async Task PostAndGetResponseAsync(
+ HttpClient client,
+ string requestUri,
+ string json)
+ {
+ var stringContent = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json);
+ var response = await client.PostAsync(requestUri, stringContent);
+
+ return response;
+ }
+}