I'm using the Microsoft.Owin.Testing library to integration test my API in-memory. I've added in the OWIN JWT middleware for my authentication needs, and am now trying to pass a generated token to test requests to controllers needing authorization. I can assure you that the JWT middleware is setup correctly, as it works just fine with normal use. However, I am observing some strange behavior with the TestServer.HttpClient object. When I set a default authorization header on HttpClient to pass the token, my tests never pass because the token is not recognized. However, when I use TestServer.CreateRequest(...), the test passes correctly and the token is recognized. I would prefer to use the HttpClient methods because they make things a hell of a lot easier with all the extension methods provided such as PostAsJsonAsync, etc. I'm beginning to think there is either a bug in the TestServer.HttpClient or I that am completely missing something.
Here's my test class (using NUnit3):
public class DefinitionsControllerTests
{
private TestServer _server;
private string _accessToken;
[SetUp]
public void Setup()
{
_server = TestServer.Create<Startup>();
var credentials = new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", "john.doe#mail.com"),
new KeyValuePair<string, string>("password", "testing123")
});
// get token from OWIN JWT middleware
dynamic resultBody = JObject.Parse(
_server.HttpClient.PostAsync("/oauth/token", credentials).Result.Content.ReadAsStringAsync().Result);
_accessToken = (string)resultBody.access_token;
// this does not appear to ever work
_server.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
}
[TearDown]
public void TearDown()
{
_server.Dispose();
}
[Test]
public void GetById_WithExistingId()
{
// 401 Unauthorized response everytime and test fails
var response = _server.HttpClient.GetAsync($"/api/definitions/{expected.Id}").Result;
var actual = response.Content.ReadAsAsync<Definition>().Result;
// 200 Ok every time and test passes
// - these variables aren't part of the test but rather to show alternate request creation method that works
var response2 = _server.CreateRequest($"/api/definitions/{expected.Id}")
.AddHeader("Authorization", "Bearer " + _accessToken)
.GetAsync()
.Result;
var actual2 = response.Content.ReadAsAsync<Definition>().Result;
response.StatusCode.ShouldBe(HttpStatusCode.OK);
actual.ShouldNotBeNull();
}
//...other test methods
}
And my controller:
[Authorize]
public class DefinitionsController : ApiController
{
private readonly IDefinitionRepository _repo;
public DefinitionsController(IDefinitionRepository repo)
{
_repo = repo;
}
public IHttpActionResult Get(Guid id)
{
var definition = _repo.Get(id);
if (definition == null)
return NotFound();
return Ok(definition);
}
}
Anyone have any idea why only CreateRequest() works? This is slightly infuriating.
The problem is that the HttpClient property returns a new instance every time. It will work if you save that instance and re-use it.
https://github.com/aspnet/AspNetKatana/blob/b850cd8b4de61e65bbd7127ce02b5df7c4cb6db5/src/Microsoft.Owin.Testing/TestServer.cs#L48
Related
I have an application where the backend is an asp.net web api and the front-end is a Blazor server side. Both projects are using net6.0.
I have implemented jwt token authentication, so users can register and login from the front-end.
My problem is that if the user refreshes a page, he automatically gets logged out. My understanding is that this can be solved using refresh token (I'm not sure if this understanding is correct).
I have tried to follow this guide: Refresh Token with Blazor WebAssembly and ASP.NET Core Web API
However since I'm using Blazor server side I cannot intercept HTTP Requests using the approach in the article.
My question is: in my Blazor server side application how can I prevent users automatically getting logged out due to page refresh and how can I intercept the http request?
UPDATE: Notice I already have everything working in regards to token and authentication between the back and frontend. The part that I'm missing is inside the blazor server side application in the program.cs file. I basically want to intercept all http request and call a method.
In program.cs I have:
builder.Services.AddScoped<IRefreshTokenService, RefreshTokenService>();
I want RefreshTokenService to be called on every http request. I have tried creating a middleware (which calls the RefreshTokenService), inside the program.cs like:
app.UseMyMiddleware();
But this only get called once.
Here's a very simplified version of an API client I'm using in my app that's also split into an ASP.NET Core API backend and a Blazor Server frontend.
The way it works is that the accessToken gets retreived from local storage and added as an authentication header to the HttpRequestMessage in my API client before each API call.
MyApiClient.cs
public class MyApiClient
{
private readonly IHttpClientFactory _clientFactory;
private readonly IMyApiTokenProvider _myApiTokenProvider;
public MyApiClient(IHttpClientFactory clientFactory, IMyApiTokenProvider myApiTokenProvider)
{
_clientFactory = clientFactory;
_myApiTokenProvider = myApiTokenProvider;
}
public async Task<ApiResponse<CustomerListResponse>> GetCustomersAsync()
{
//create HttpClient
var client = _clientFactory.CreateClient("MyApiHttpClient");
//create HttpRequest
var request = CreateRequest(HttpMethod.Get, "/getCustomers");
//call the API
var response = await client.SendAsync(request);
//if Unauthorized, refresh access token and retry
if(response.StatusCode == HttpStatusCode.Unauthorized)
{
var refreshResult = await RefreshAccessToken(client);
if (refreshResult.IsSuccess)
{
//save new token
await _backendTokenProvider.SetAccessToken(refreshResult.NewAccessToken);
//create request again, with new access token
var retryRequest = await CreateRequest(HttpMethod.Get, "/getCustomers");
//retry
response = await client.SendAsync(retryRequest);
}
else
{
//refresh token request failed
return ApiResponse<CustomerListResponse>.Error("Token invalid");
}
}
//parse response
var customers = await response.Content.ReadFromJsonAsync<ApiResponse<CustomerListResponse>>();
return customers;
}
private HttpRequestMessage CreateRequest<TRequest>(string command, HttpMethod method, TRequest requestModel = null) where TRequest : class
{
//create HttpRequest
var request = new HttpRequestMessage(method, command);
//add body if not empty
if (requestModel is not null)
{
request.Content = JsonContent.Create(requestModel);
}
//set the Auth header to the Access Token value taken from Local Storage
var accessToken = await _myApiTokenProvider.GetAccessToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return request;
}
private async Task<ApiResponse<RefreshTokenResponse>> RefreshAccessToken(HttpClient client)
{
var refreshToken = await _backendTokenProvider.GetRefreshToken();
if (refreshToken is null)
{
return ApiResponse<RefreshTokenResponse>.Error("Refresh token is null, cannot refresh access token");
}
var refreshRequest = CreateRequest(HttpMethod.Post, "/refreshToken", new RefreshTokenRequest(refreshToken));
var refreshResponse = await client.SendAsync(refreshRequest);
var refreshResult = await response.Content.ReadFromJsonAsync<ApiResponse<RefreshTokenResponse>>();
return refreshResult;
}
}
MyApiTokenProvider.cs
public class MyApiTokenProvider : IMyApiTokenProvider
{
private readonly ProtectedLocalStorage _protectedLocalStorage;
public MyApiTokenProvider(ProtectedLocalStorage protectedLocalStorage)
{
_protectedLocalStorage = protectedLocalStorage;
}
public async Task<string> GetAccessToken()
{
var result = await _protectedLocalStorage.GetAsync<string>("accessToken");
return result.Success ? result.Value : null;
}
public async Task<string> GetRefreshToken()
{
var result = await _protectedLocalStorage.GetAsync<string>("refreshToken");
return result.Success ? result.Value : null;
}
public async Task SetAccessToken(string newAccessToken)
{
await _protectedLocalStorage.SetAsync("accessToken", newAccessToken);
}
public async Task SetRefreshToken(string newRefreshToken)
{
await _protectedLocalStorage.SetAsync("refreshToken", newRefreshToken);
}
}
I am doing some integration testing of my web API that uses NancyFX end points. I have the xUnit test create a test server for the integration test
private readonly TestServer _server;
private readonly HttpClient _client;
public EventsModule_Int_Tester()
{
//Server setup
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_server.AllowSynchronousIO = true;//Needs to be overriden in net core 3.1
_client = _server.CreateClient();
}
Inside a Test Method I tried the following
[Fact]
public async Task EventTest()
{
// Arrange
HttpResponseMessage expectedRespone = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
var data = _server.Services.GetService(typeof(GenijalnoContext)) as GenijalnoContext;
//Get come random data from the DBcontext
Random r = new Random();
List<Resident> residents = data.Residents.ToList();
Resident random_residnet = residents[r.Next(residents.Count)];
List<Apartment> apartments = data.Apartments.ToList();
Apartment random_Apartment = apartments[r.Next(apartments.Count)];
EventModel model = new EventModel()
{
ResidentId = random_residnet.Id,
ApartmentNumber = random_Apartment.Id
};
//Doesnt work
IList<KeyValuePair<string, string>> nameValueCollection = new List<KeyValuePair<string, string>> {
{ new KeyValuePair<string, string>("ResidentId", model.ResidentId.ToString()) },
{ new KeyValuePair<string, string>("ApartmentNumber", model.ApartmentNumber.ToString())}
};
var result = await _client.PostAsync("/Events/ResidentEnter", new FormUrlEncodedContent(nameValueCollection));
//Also Doesnt work
string json = JsonConvert.SerializeObject(model, Formatting.Indented);
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _client.PostAsync("/Events/ResidentEnter", httpContent);
//PostAsJsonAsync also doesnt work
// Assert
Assert.Equal(response.StatusCode, expectedRespone.StatusCode);
}
The NancyFX module does trigger the endpoint and receives the request but without the body
What am I doing wrong? Note that the NancyFX endpoint has no issue transforming a Postman call into a valid model.
The NancyFX endpoint
Alright I fixed it, for those curious the issue was that the NancyFX body reader sometimes does not properly start reading the request body. That is that the stream reading position isn't 0 (the start) all the time.
To fix this you need to create a CustomBoostrapper and then override the ApplicationStartup function so you can set up a before request pipeline that sets the body position at 0
Code below
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
pipelines.BeforeRequest.AddItemToStartOfPipeline(ctx =>
{
ctx.Request.Body.Position = 0;
return null;
});
}
I'm in the process of trying to put together a small project which uses Asp.Net Core Identity, Identity Server 4 and a Web API project.
I've got my MVC project authenticating correctly with IdS4 from which I get a JWT which I can then add to the header of a request to my Web API project, this all works as expected.
The issue I have is how I'm actually adding the token to the HttpClient, basically I'm setting it up for every request which is obviously wrong otherwise I'd have seen other examples online, but I haven't been able to determine a good way to refactor this. I've read many articles and I have found very little information about this part of the flow, so I'm guessing it could be so simple that it's never detailed in guides, but I still don't know!
Here is an example MVC action that calls my API:
[HttpGet]
[Authorize]
public async Task<IActionResult> GetFromApi()
{
var client = await GetHttpClient();
string testUri = "https://localhost:44308/api/TestItems";
var response = await client.GetAsync(testUri, HttpCompletionOption.ResponseHeadersRead);
var data = await response.Content.ReadAsStringAsync();
GetFromApiViewModel vm = new GetFromApiViewModel()
{
Output = data
};
return View(vm);
}
And here is the GetHttpClient() method which I call (currently residing in the same controller):
private async Task<HttpClient> GetHttpClient()
{
var client = new HttpClient();
var expat = HttpContext.GetTokenAsync("expires_at").Result;
var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
if ((dataExp - DateTime.Now).TotalMinutes < 10)
{
//SNIP GETTING A NEW TOKEN IF ITS ABOUT TO EXPIRE
}
var accessToken = await HttpContext.GetTokenAsync("access_token");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return client;
}
My StartUp classes are pretty standard from what I gather, but if they could be useful, then I'll add them in.
I've read many articles and I have found very little information about this part of the flow, so I'm guessing it could be so simple that it's never detailed in guides, but I still don't know!
The problem is that the docs are really spread all over, so it's hard to get a big picture of all the best practices. I'm planning a blog series on "Modern HTTP API Clients" that will collect all these best practices.
First, I recommend you use HttpClientFactory rather than new-ing up an HttpClient.
Next, adding an authorization header is IMO best done by hooking into the HttpClient's pipeline of message handlers. A basic bearer-token authentication helper could look like this:
public sealed class BackendApiAuthenticationHttpClientHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _accessor;
public BackendApiAuthenticationHttpClientHandler(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var expat = await _accessor.HttpContext.GetTokenAsync("expires_at");
var dataExp = DateTime.Parse(expat, null, DateTimeStyles.RoundtripKind);
if ((dataExp - DateTime.Now).TotalMinutes < 10)
{
//SNIP GETTING A NEW TOKEN IF ITS ABOUT TO EXPIRE
}
var token = await _accessor.HttpContext.GetTokenAsync("access_token");
// Use the token to make the call.
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
}
This can be hooked up via DI:
services.AddTransient<BackendApiAuthenticationHttpClientHandler>();
services.AddHttpClient<MyController>()
.ConfigureHttpClient((provider, c) => c.BaseAddress = new Uri("https://localhost:44308/api"))
.AddHttpMessageHandler<BackendApiAuthenticationHttpClientHandler>();
Then you can inject an HttpClient into your MyController, and it will magically use the auth tokens:
// _client is an HttpClient, initialized in the constructor
string testUri = "TestItems";
var response = await _client.GetAsync(testUri, HttpCompletionOption.ResponseHeadersRead);
var data = await response.Content.ReadAsStringAsync();
GetFromApiViewModel vm = new GetFromApiViewModel()
{
Output = data
};
return View(vm);
This pattern seems complex at first, but it separates the "how do I call this API" logic from "what is this action doing" logic. And it's easier to extend with retries / circuit breakers / etc, via Polly.
You can use HttpRequestMessage
// Create this instance once on stratup
// (preferably you want to keep an instance per base url to avoid waiting for socket fin)
HttpClient client = new HttpClient();
Then create an instance of HttpRequestMessage:
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Get,
"https://localhost:44308/api/TestItems");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "ey..");
await client.SendAsync(request);
I am trying to build a service client to simplify calling my microservices in .net core.
Here is a service client sample:
public ProductServiceClient(SystemEnvironment.MachineEnvironment? environment = null)
{
this.url = ServiceEnvironment.Urls.GetUrl(ServiceEnvironment.Service.Product, environment);
}
private RestClient GetClient(string method)
{
return new RestClient(url + "/api/" + method);
}
private RestRequest GetRestRequest(Method method)
{
var restRequest = new RestRequest(method);
restRequest.RequestFormat = DataFormat.Json;
restRequest.AddHeader("Content-Type", "application/json");
return restRequest;
}
public FindProductsResponse FindProducts(FindProductsRequest request)
{
var restRequest = GetRestRequest(Method.GET);
restRequest.AddJsonBody(request);
var client = this.GetClient("Products");
var restResponse = client.Get(restRequest);
return new JsonDeserializer().Deserialize<FindProductsResponse>(restResponse);
}
public void Dispose()
{
}
And here is how I am trying to read it in my .net core api:
[HttpGet]
public ActionResult<FindProductsResponse> Get()
{
var request = "";
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
request = reader.ReadToEnd();
}
var buildRequest = JsonConvert.DeserializeObject<FindProductsRequest>(request);
var products = _service.FindProducts(buildRequest);
if (products != null && products.Any())
{
return new FindProductsResponse()
{
Products = products
};
}
return BadRequest("Not found");
}
However the request variable is always empty after Request.Body has been processed by the StreamReader.
If I make the same request from Postman (also using GET), I get the body just fine.
What am I doing wrong here?
EDIT: This is the unit test calling the api:
[Test]
public void Test1()
{
using (var productServiceClient = new ProductServiceClient())
{
var products = productServiceClient.FindProducts(new FindProductsRequest()
{
Id = 50
}).Products;
}
}
It can be your Request.Body has been already consumed.
Try to call Request.EnableRewind() before to open the StreamReader.
I'm not sure why you are manually doing it. It looks like you are reinventing the wheel. ASP.NET Core already does that for you.
This is what your service should look like:
[HttpGet] // oops, GET requests will not allow Bodies, this won't work
public ActionResult<FindProductsResponse> Get([FromBody]FindProductsRequest buildRequest)
{
// skip all the serialization stuff, the framework does that for you
var products = _service.FindProducts(buildRequest);
if (products != null && products.Any())
{
return new FindProductsResponse()
{
Products = products
};
}
return BadRequest("Not found");
}
And if you don't want to redo all the busy work that is retyping all the code on the client side, I suggest you read up on swagger (probably in the form of Swashbuckle). Client code can be generated. Even from within Visual Studio, if you right-click on the project and in the context menu pick "Add REST API Client...". Please don't erroneously hand-code what can be generated flawlessly by a machine instead. I don't really know what went wrong in your specific case, but searching bugs that could be avoided altogether is just busywork, that time should be spent on other parts of the program.
I just realized this is a GET request. ASP.NET will not recognize bodies for GET-Requests. You will need to make it a PUT or POST request or put your parameters in the query string.
If you happen to make that mistake as often as I did, you might want to write some unit tests that cover this. Because .NET is not helping you there. Been there, done that..
I can't work out the correct way to pass objects from HandleAuthenticateAsync , where my custom authentication code is, to the HandleChallengeAsync callback, where I handle the authentication failure and set the response status code and body.
If authentication fails, I want to return a JSON object which includes some information in the response body about why authentication failed.
In my handler code, I want to serialize some values from results in the HandleChallengeAsync method:
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
public const string ApiKeyHeaderName = "X-API-KEY";
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { }
// The authentication handler that gets call for every request (including `[AllowAnonymous]`
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
Request.Headers.TryGetValue(Options.ApiKeyHeaderName, out var apiKeyHeaderValues);
var key = apiKeyHeaderValues.FirstOrDefault();
var results = await new AuthenticateApiKeyQuery(key).Results();
if (results.HasErrors())
return AuthenticateResult.Fail("Useless message that doesn't go anywhere");
return Success(results.Value);
}
// the callback that gets called for any `[Authorize]` actions
override protected async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var errors = new { }; // I want `errors` to come from the `result` object above
Context.Response.StatusCode = 401;
Context.Response.ContentType = "application/json";
var json = Newtonsoft.Json.JsonConvert.SerializeObject(errors);
await Context.Response.Body.WriteAsync(System.Text.Encoding.UTF8.GetBytes(json));
}
// Method that sent up the identity/claim etc
private AuthenticateResult Success(ApiKey apiKey)
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, apiKey.ID.ToString()),
new Claim(ClaimTypes.Name, apiKey.Description),
};
var identity = new ClaimsIdentity(claims, Options.AuthenticationType);
var identities = new List<ClaimsIdentity> { identity };
var principal = new ClaimsPrincipal(identities);
var ticket = new AuthenticationTicket(principal, Options.AuthenticationScheme);
return AuthenticateResult.Success(ticket);
}
My initial attempt had the HandleAuthenticateAsync method set the 401 and response body. This does not work as the authentication handler is also run for actions tagged with [AllowAnonymous]. The result is that the response will be 401, and the serialized JSON, rather than the status and response set by the action. Whereas, HandleChallengeAsync, is only called when authentication fails and the action requires authorization.
This isn't the easiest of code to navigate, but from what I can tell, PolicyEvalulator calls AuthenticationHandler which then calls my custom HandleAuthenticateAsync. The AuthenticateResult I return then gets swallowed up by the PolicyEvalulator, so I cant use it to hold any values for later processing. I haven't yet worked out what calls my custom HandleChallengeAsync, but by this point the AuthenticateResult has been swallowed up.
I faced the similar issues, The authentication handler is transient-scoped, you can declare a object type property in your Custom Handler class and that will be available in HandleChallengeAsync method.