i woudl add 2 api into the controller of net core 3.1 api project:
first one accept access_token as parameter, the other one, called whitout parameters, retrives token from headers.
this the controller:
[Route("entitlements")]
public class EntitlementsController : ControllerBase
{
[Route("entitlements/get/{access_token}")]
public async Task<IActionResult> Get([FromQuery] string access_token)
{
EntitlementsResult oResult = GetEntitlements(access_token);
return new JsonResult(oResult);
}
[Route("entitlements")]
public async Task<IActionResult> Get()
{
var access_token = await GetAccessTokenFromHeaders();
EntitlementsResult oResult = GetEntitlements(token);
return new JsonResult(oResult);
}
}
but i get 404 not found calling the action from a client with this code:
public async Task<IActionResult> GetEntitlements()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
string apiUrl = Startup.StaticConfig.GetValue<string>("IdentityServer:api");
var content = await client.GetStringAsync($"{apiUrl}/entitlements");
ViewBag.Json = content;
return View("json");
}
For both of the action and controller contains [Route("entitlements")] attribute,so the correct url should be:{apiUrl}/[ControllerRoute]/[ActionRoute],change like below:
var content = await client.GetStringAsync($"{apiUrl}/entitlements/entitlements");
Related
I have a running Web API and I try to get a bearer token back from it. Starting the request from Postman is working and I get the token back. Once doing from my application I always get an http 400 Bad Request error.
What am I missing here?
public async Task<string> GetToken(string userName, string passWord)
{
var request = new HttpRequestMessage(HttpMethod.Post, "api/auth/login");
request.Headers.Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ userName}:{ passWord}")));
request.Headers.Host = "api.my-host.com";
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
var authResult = await JsonSerializer.DeserializeAsync<AuthResult>(responseStream);
return authResult == null ? "" : authResult.Access_Token;
}
As requested here's a screenshot of the Postman result:
I created a HttpGet request and added the bearer token from Postman in the code and I receive data. Just the token request seems to have a problem.
And my controller:
namespace AmsAPI.Controller
{
[Produces("application/json")]
[Route("api/auth")]
[ApiController]
[Authorize]
public class AuthenticationController : ControllerBase
{
private readonly IAuthenticationManager _authenticationManager;
public AuthenticationController(IAuthenticationManager authenticationManager)
{
_authenticationManager = authenticationManager;
}
[HttpPost("login"), AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult> Login([FromHeader] byte[] basic)
{
if (!ModelState.IsValid) return BadRequest();
string Basic = Encoding.UTF8.GetString(basic);
var splitBasic = Basic.Split(':');
AuthCredentials credentials = new()
{
UserName = splitBasic[0],
Password = splitBasic[1]
};
return await _authenticationManager.SignInCheck(credentials) ?
Ok(new
{
message = string.Format("User {0} successfully logged in.", credentials.UserName),
access_token = await _authenticationManager.CreateToken(),
token_type = "bearer",
expires_in = "3600"
}) :
Unauthorized();
}
[HttpGet("user")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<List<User>> GetUser() => await _authenticationManager.GetUser();
}
}
Well I have successfully reproduce your issue.
You are getting 400 because its searching for SSL credentials but
by default we don't have certificate bind with our request and its not
required. So to handle this 400 exception you have to use
HttpClientHandler which will bypass certificate error. So you can
try below way to get the token response successfully.
HTTP Request:
public async Task<string> GetToken()
{
var user = new UserCred();
user.user_name = "admin";
user.password = "123456";
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
var client = new HttpClient(handler);
var json = JsonConvert.SerializeObject(user);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = "http://localhost:21331/api/Authentication/login";
var response = await client.PostAsync(url, data);
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
return result;
}
Output:
Note: You need to add below code snippet to resolve your issue. But I like to call HTTP POST request in above ways to make it little
clean. You can continue with your code just adjust what I suggest.
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
var client = new HttpClient(handler);
Hope above steps resolve your problem accordingly.
It appeared that Postman is sending the Basic Authorization Header as Content of the Http request and not in the Header, but my Web App was implementing the Authorization correctly in the Header.
So in the WebApi it looks like
[Authorize]
[Route("api/auth")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly IAuthenticationManager _authenticationManager;
public AuthenticationController(IAuthenticationManager authenticationManager)
{
_authenticationManager = authenticationManager;
}
[HttpPost("login"), AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult> Login()
{
//check if header has Authorization
if (!Request.Headers.ContainsKey("Authorization")) return BadRequest();
try
{
AuthenticationHeaderValue authenticationHeaderValue = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
if (authenticationHeaderValue == null) throw new Exception();
var bytes = Convert.FromBase64String(authenticationHeaderValue.Parameter ?? throw new Exception());
string[] creds = Encoding.UTF8.GetString(bytes).Split(':');
AuthCredentials credentials = new()
{
UserName = creds[0],
Password = creds[1]
};
return await _authenticationManager.SignInCheck(credentials) ?
Ok(new
{
message = string.Format("User {0} successfully logged in.", credentials.UserName),
access_token = await _authenticationManager.CreateToken(),
token_type = "bearer",
expires_in = "3600"
}) :
Unauthorized();
}
catch (Exception)
{
return BadRequest();
}
}
}
and my request like following:
public async Task<string> GetToken(string userName, string passWord)
{
//set request
var request = new HttpRequestMessage(HttpMethod.Post, "api/auth/login");
//set Header
request.Headers.Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ userName}:{ passWord}")));
//get response
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
var authResult = await JsonSerializer.DeserializeAsync<AuthResult>(responseStream);
var token = authResult.Access_Token;
return authResult == null ? "Authorization failed!" : "Bearer token successfully created!";
}
and the httpClient is outsourced into a Service
public static void ConfigureServices(this IServiceCollection services)
{
var url = Environment.GetEnvironmentVariable("amsApiUrl");
var host = Environment.GetEnvironmentVariable("amsHostUrl");
//set HttpClient
services.AddHttpClient<IAmsAccountService, AmsAccountService>(c =>
{
c.BaseAddress = new Uri(url ?? "");
c.DefaultRequestHeaders.Accept.Clear();
c.DefaultRequestHeaders.Host = host;
c.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
c.Timeout = TimeSpan.FromSeconds(10);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
CookieContainer = new CookieContainer(),
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) =>
{
return true;
}
};
});
}
I wrote a controller. I wrote it according to the web api I will use here. But how should I make my own created api?
Do I need to have my own created api where I write with HttpPost? I might be wrong as I am new to this.
public class GoldPriceDetailController : Controller
{
string Baseurl = "https://apigw.bank.com.tr:8003/";
public async Task<ActionResult> GetGoldPrice()
{
List<GoldPrice> goldPriceList = new List<GoldPrice>();
using (var client = new HttpClient())
{
//Passing service base url
client.BaseAddress = new Uri(Baseurl);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Sending request to find web api REST service resource GetDepartments using HttpClient
HttpResponseMessage Res = await client.GetAsync("getGoldPrices");
Console.WriteLine(Res.Content);
//Checking the response is successful or not which is sent using HttpClient
if (Res.IsSuccessStatusCode)
{
var ObjResponse = Res.Content.ReadAsStringAsync().Result;
goldPriceList = JsonConvert.DeserializeObject<List<GoldPrice>>(ObjResponse);
}
//returning the student list to view
return View(goldPriceList);
}
}
[HttpPost]
public async Task<IActionResult> GetReservation(int id)
{
GoldPrice reservation = new GoldPrice();
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.GetAsync("https://apigw.bank.com.tr:8443/getGoldPrices" + id))
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string apiResponse = await response.Content.ReadAsStringAsync();
reservation = JsonConvert.DeserializeObject<GoldPrice>(apiResponse);
}
else
ViewBag.StatusCode = response.StatusCode;
}
}
return View(reservation);
}
}
Basically you need these steps:
HttpClient for local API.
HttpClient for external API.
Local API controller.
Inject external client into the local API.
Then inject the local API client into the razor page/controller.
HttpClient for Local API
public class LocalApiClient : ILocalHttpClient
{
private readonly HttpClient _client;
public LocalAPiClient(HttpClient client)
{
_client = client;
_client.BaseAddress(new Uri("https://localapi.com"));
}
[HttpGet]
public async Task<string> GetGoldPrices(int id)
{
// logic to get prices from local api
var response = await _client.GetAsync($"GetGoldPrices?id={id}");
// deserialize or other logic
}
}
HttpClient for External API
public class ExternalApiClient : IExternalHttpClient
{
private readonly HttpClient _client;
public ExternalAPiClient(HttpClient client)
{
_client = client;
_client.BaseAddress(new Uri("https://externalApi.com"));
// ...
}
[HttpGet]
public async Task<string> GetGoldPrices(int id)
{
// logic to get prices from external api
var response = await _client.GetAsync("getGoldPrices?id=" + id))
}
}
Register your clients in startup
services.AddHttpClient<ILocalHttpClient, LocalHttpClient>();
services.AddHttpClient<IExternalHttpClient, ExternalHttpClient>();
Create Local API Controller
and inject the external http client into it
[ApiController]
public class LocalAPIController : Controller
{
private readonly IExternalHttpClient _externalClient;
public LocalAPIController(IExternalHttpClient externalClient)
{
_externalClient = externalClient;
}
[HttpGet]
public async Task<string> GetGoldPrices(int id)
{
var resoponse = await _externalClient.GetGoldPrices(id);
// ...
}
}
Inject the local client into razor page/controller
public class HomeController : Controller
{
private readonly ILocalHttpClient _localClient;
public HomeController(ILocalHttpClient localClient)
{
_localClient = localClient;
}
public async Task<IActionResult> Index(int id)
{
var response = await _localClient.GetGoldPrices(id);
// ...
}
}
I recently upgraded my project from Asp.Net Core 3.0 to the latest build Asp.Net Core 3.1.0 preview 3.
My project is running Blazor in the client, and I also intercept the pipeline to add a Jwt to the header of every request sent back to the server using my JwtTokenHeaderHandler which extends DelegatingHandler.
I add this in the startup.cs of my blazor.client project as follows;
services.AddSingleton<HttpClient>(s =>
{
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var uriHelper = s.GetRequiredService<NavigationManager>();
var myHandler = s.GetRequiredService<JwtTokenHeaderHandler>();
myHandler.InnerHandler = new WebAssemblyHttpMessageHandler();
return new HttpClient(myHandler)
{
BaseAddress = new Uri(uriHelper.BaseUri)
};
});
However, since the upgrade it appears that WebAssemblyHttpMessageHandler() is no longer available?
I cannot find any documentation as to how to fix this approach.
Can anyone advise on how I can correct this please?
Update based on Aqua's answer
After adding the following code to my Startup.cs;
services.AddTransient(p =>
{
var wasmHttpMessageHandlerType = Assembly.Load("WebAssembly.Net.Http")
.GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
var constructor = wasmHttpMessageHandlerType.GetConstructor(Array.Empty<Type>());
return constructor.Invoke(Array.Empty<object>()) as HttpMessageHandler;
})
.AddTransient<JwtTokenHeaderHandler>();
Calling an Authorized Get endpoint from my .razor file;
var response = await Http.GetAsync<ContactTypeOutputModel[]>("ContactTypes/all");
(where the above is simply a typed wrapped service response using an extension method);
public static async Task<ServiceResponse<T>> GetAsync<T>(
this HttpClient httpClient, string url)
{
var response = await httpClient.GetAsync(url);
return await BuildResponse<T>(response);
}
I am still getting a 401 on the service? and no output logging to say the service is being used?
public class JwtTokenHeaderHandler : DelegatingHandler
{
private readonly IAppLocalStorageService _localStorage;
private readonly ILogger<JwtTokenHeaderHandler> _logger;
private readonly HttpMessageHandler _innerHandler;
private readonly MethodInfo _method;
public JwtTokenHeaderHandler(IAppLocalStorageService localStorage, HttpMessageHandler innerHandler, ILogger<JwtTokenHeaderHandler> logger)
{
_localStorage = localStorage;
_logger = logger;
_innerHandler = innerHandler;
var type = innerHandler.GetType();
_method = type.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod) ?? throw new InvalidOperationException("Cannot get SendAsync method");
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
_logger.LogDebug("Adding Jwt to Header from JwtTokenHeaderHandler: SendAsync");
_logger.LogDebug($"Does header contain 'bearer' token: {!request.Headers.Contains("bearer")}");
if (!request.Headers.Contains("bearer"))
{
_logger.LogDebug("Adding Bearer Token to Header");
var savedToken = await _localStorage.GetTokenAsync();
_logger.LogDebug($"Saved Token is : {savedToken}");
if (!string.IsNullOrWhiteSpace(savedToken))
{
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
}
}
//return await base.SendAsync(request, cancellationToken);
return await (_method.Invoke(_innerHandler, new object[] { request, cancellationToken }) as Task<HttpResponseMessage>);
}
}
Any ideas why this is not being hit?
WebAssemblyHttpMessageHandler has been replaced by Mono's WasmHttpMessageHandler by you can't add a dependency to WebAssembly.Net.Http.
To create a DependencyHandler for blazor WASM, I did a little hack :
public class OidcDelegationHandler : DelegatingHandler
{
private readonly IUserStore _userStore;
private readonly HttpMessageHandler _innerHanler;
private readonly MethodInfo _method;
public OidcDelegationHandler(IUserStore userStore, HttpMessageHandler innerHanler)
{
_userStore = userStore ?? throw new ArgumentNullException(nameof(userStore));
_innerHanler = innerHanler ?? throw new ArgumentNullException(nameof(innerHanler));
var type = innerHanler.GetType();
_method = type.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod) ?? throw new InvalidOperationException("Cannot get SendAsync method");
WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue(_userStore.AuthenticationScheme, _userStore.AccessToken);
return _method.Invoke(_innerHanler, new object[] { request, cancellationToken }) as Task<HttpResponseMessage>;
}
}
Is set it up in DI with:
return services
.AddTransient(p =>
{
var wasmHttpMessageHandlerType = Assembly.Load("WebAssembly.Net.Http")
.GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler");
var constructor = wasmHttpMessageHandlerType.GetConstructor(Array.Empty<Type>());
return constructor.Invoke(Array.Empty<object>()) as HttpMessageHandler;
})
The full code is here
In .Net Core 2.2. I am creating a API Controller that routes the request to another Http endpoint based on payload.
[Route("api/v1")]
public class RoutesController : Controller
{
[HttpPost]
[Route("routes")]
public async Task<IActionResult> Routes([FromBody]JObject request)
{
var httpClient = new HttpClient();
// here based on request httpCLient will make `POST` or `GET` or `PUT` request
// and returns `Task<HttpResponseMessage>`. Lets assume its making `GET`
// call
Task<HttpResponseMessage> response = await
httpClient.GetAsync(request["resource"]);
/* ??? what is the correct way to return response as `IActionResult`*/
}
}
based on SO post i can do this
return StatusCode((int)response.StatusCode, response);
However i am not sure sending HttpResponseMessage as ObjectResult is correct way.
I also want to make sure content negotiation will work.
Update 7/25/2022
Updated the correct answer
public class HttpResponseMessageResult : IActionResult
{
private readonly HttpResponseMessage _responseMessage;
public HttpResponseMessageResult(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage; // could add throw if null
}
public async Task ExecuteResultAsync(ActionContext context)
{
var response = context.HttpContext.Response;
if (_responseMessage == null)
{
var message = "Response message cannot be null";
throw new InvalidOperationException(message);
}
using (_responseMessage)
{
response.StatusCode = (int)_responseMessage.StatusCode;
var responseFeature = context.HttpContext.Features.Get<IHttpResponseFeature>();
if (responseFeature != null)
{
responseFeature.ReasonPhrase = _responseMessage.ReasonPhrase;
}
var responseHeaders = _responseMessage.Headers;
// Ignore the Transfer-Encoding header if it is just "chunked".
// We let the host decide about whether the response should be chunked or not.
if (responseHeaders.TransferEncodingChunked == true &&
responseHeaders.TransferEncoding.Count == 1)
{
responseHeaders.TransferEncoding.Clear();
}
foreach (var header in responseHeaders)
{
response.Headers.Append(header.Key, header.Value.ToArray());
}
if (_responseMessage.Content != null)
{
var contentHeaders = _responseMessage.Content.Headers;
// Copy the response content headers only after ensuring they are complete.
// We ask for Content-Length first because HttpContent lazily computes this
// and only afterwards writes the value into the content headers.
var unused = contentHeaders.ContentLength;
foreach (var header in contentHeaders)
{
response.Headers.Append(header.Key, header.Value.ToArray());
}
await _responseMessage.Content.CopyToAsync(response.Body);
}
}
}
You can create a custom IActionResult that will wrap transfere logic.
public async Task<IActionResult> Routes([FromBody]JObject request)
{
var httpClient = new HttpClient();
HttpResponseMessage response = await httpClient.GetAsync("");
// Here we ask the framework to dispose the response object a the end of the user resquest
this.HttpContext.Response.RegisterForDispose(response);
return new HttpResponseMessageResult(response);
}
public class HttpResponseMessageResult : IActionResult
{
private readonly HttpResponseMessage _responseMessage;
public HttpResponseMessageResult(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage; // could add throw if null
}
public async Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_responseMessage.StatusCode;
foreach (var header in _responseMessage.Headers)
{
context.HttpContext.Response.Headers.TryAdd(header.Key, new StringValues(header.Value.ToArray()));
}
if(_responseMessage.Content != null)
{
using (var stream = await _responseMessage.Content.ReadAsStreamAsync())
{
await stream.CopyToAsync(context.HttpContext.Response.Body);
await context.HttpContext.Response.Body.FlushAsync();
}
}
}
}
ASP.NET Core has the return object RedirectResult to redirect the caller.
Simply wrap the response in Ok() Action return type:
return Ok(response)
so your code would look something like:
[Route("api/v1")]
public class RoutesController : Controller
{
[HttpPost]
[Route("routes")]
public async Task<IActionResult> Routes([FromBody]JObject request)
{
var httpClient = new HttpClient();
Task<HttpResponseMessage> response = await httpClient.GetAsync(request["resource"]);
return Ok(response);
}
}
More info here: https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1
I am looking for some guidance...
I'm currently looking at trying to write some integration tests for a Razor Pages app in .net core 2.1, the pages I'm wanting to test are post authentication but I'm not sure about the best way of approaching it. The docs seem to suggest creating a CustomWebApplicationFactory, but apart from that I've got a little bit lost as how I can fake/mock an authenticated user/request, using basic cookie based authentication.
I've seen that there is an open GitHub issue against the Microsoft docs (here is the actual GitHub issue), there was a mentioned solution using IdentityServer4 but I’m just looking how to do this just using cookie based authentication.
Has anyone got any guidance they may be able to suggest?
Thanks in advance
My Code so far is:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseMySql(connectionString);
options.EnableSensitiveDataLogging();
});
services.AddLogging(builder =>
{
builder.AddSeq();
});
services.ConfigureAuthentication();
services.ConfigureRouting();
}
}
ConfigureAuthentication.cs
namespace MyCarparks.Configuration.Startup
{
public static partial class ConfigurationExtensions
{
public static IServiceCollection ConfigureAuthentication(this IServiceCollection services)
{
services.AddIdentity<MyCarparksUser, IdentityRole>(cfg =>
{
//cfg.SignIn.RequireConfirmedEmail = true;
})
.AddDefaultUI()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
options.Conventions.AuthorizeFolder("/Sites");
});
return services;
}
}
}
Integration Tests
PageTests.cs
namespace MyCarparks.Web.IntegrationTests
{
public class PageTests : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly CustomWebApplicationFactory<Startup> factory;
public PageTests(CustomWebApplicationFactory<Startup> webApplicationFactory)
{
factory = webApplicationFactory;
}
[Fact]
public async Task SitesReturnsSuccessAndCorrectContentTypeAndSummary()
{
var siteId = Guid.NewGuid();
var site = new Site { Id = siteId, Address = "Test site address" };
var mockSite = new Mock<ISitesRepository>();
mockSite.Setup(s => s.GetSiteById(It.IsAny<Guid>())).ReturnsAsync(site);
// Arrange
var client = factory.CreateClient();
// Act
var response = await client.GetAsync("http://localhost:44318/sites/sitedetails?siteId=" + siteId);
// Assert
response.EnsureSuccessStatusCode();
response.Content.Headers.ContentType.ToString()
.Should().Be("text/html; charset=utf-8");
var responseString = await response.Content.ReadAsStringAsync();
responseString.Should().Contain("Site Details - MyCarparks");
}
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseStartup<Startup>();
}
}
}
For implement your requirement, you could try code below which creates the client with the authentication cookies.
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint> where TEntryPoint : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
});
base.ConfigureWebHost(builder);
}
public new HttpClient CreateClient()
{
var cookieContainer = new CookieContainer();
var uri = new Uri("https://localhost:44344/Identity/Account/Login");
var httpClientHandler = new HttpClientHandler
{
CookieContainer = cookieContainer
};
HttpClient httpClient = new HttpClient(httpClientHandler);
var verificationToken = GetVerificationToken(httpClient, "https://localhost:44344/Identity/Account/Login");
var contentToSend = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("Email", "test#outlook.com"),
new KeyValuePair<string, string>("Password", "1qaz#WSX"),
new KeyValuePair<string, string>("__RequestVerificationToken", verificationToken),
});
var response = httpClient.PostAsync("https://localhost:44344/Identity/Account/Login", contentToSend).Result;
var cookies = cookieContainer.GetCookies(new Uri("https://localhost:44344/Identity/Account/Login"));
cookieContainer.Add(cookies);
var client = new HttpClient(httpClientHandler);
return client;
}
private string GetVerificationToken(HttpClient client, string url)
{
HttpResponseMessage response = client.GetAsync(url).Result;
var verificationToken =response.Content.ReadAsStringAsync().Result;
if (verificationToken != null && verificationToken.Length > 0)
{
verificationToken = verificationToken.Substring(verificationToken.IndexOf("__RequestVerificationToken"));
verificationToken = verificationToken.Substring(verificationToken.IndexOf("value=\"") + 7);
verificationToken = verificationToken.Substring(0, verificationToken.IndexOf("\""));
}
return verificationToken;
}
}
Following Chris Pratt suggestion, but for Razor Pages .NET Core 3.1 I use a previous request to authenticate against login endpoint (which is also another razor page), and grab the cookie from the response. Then I add the same cookie as part of the http request, and voila, it's an authenticated request.
This is a piece of code that uses an HttpClient and AngleSharp, as the official microsoft documentation, to test a razor page. So I reuse it to grab the cookie from the response.
private async Task<string> GetAuthenticationCookie()
{
var formName = nameof(LoginModel.LoginForm); //this is the bounded model for the login page
var dto =
new Dictionary<string, string>
{
[$"{formName}.Username"] = "foo",
[$"{formName}.Password"] = "bar",
};
var page = HttpClient.GetAsync("/login").GetAwaiter().GetResult();
var content = HtmlHelpers.GetDocumentAsync(page).GetAwaiter().GetResult();
//this is the AndleSharp
var authResult =
await HttpClient
.SendAsync(
(IHtmlFormElement)content.QuerySelector("form[id='login-form']"),
(IHtmlButtonElement)content.QuerySelector("form[id='login-form']")
.QuerySelector("button"),
dto);
_ = authResult.Headers.TryGetValues("Set-Cookie", out var values);
return values.First();
}
Then that value can be reused and passed with the new http request.
//in my test, the cookie is a given (from the given-when-then approach) pre-requirement
protected void Given()
{
var cookie = GetAuthenticationCookie().GetAwaiter().GetResult();
//The http client is a property that comes from the TestServer, when creating a client http for tests as usual. Only this time I set the auth cookie to it
HttpClient.DefaultRequestHeaders.Add("Set-Cookie", cookie);
var page = await HttpClient.GetAsync($"/admin/protectedPage");
//this will be a 200 OK because it's an authenticated request with whatever claims and identity the /login page applied
}