Reputation: 2427
I have created this class for getting the Header value from requests.
public class AuthenticationHeader
{
private static IHttpContextAccessor _httpContextAccessor;
public AuthenticationHeader(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string AuthHeader => _httpContextAccessor.HttpContext?.Request.Headers["Authorization"];
}
and that I have registered that in my startup.cs like this
services.AddSingleton<AuthenticationHeader>();
And its been injected into my other classes like this.
public BaseClient(HttpClient client, ILogger<BaseClient> logger, AuthenticationHeader authHeader)
{
_client = client;
client.BaseAddress = new Uri("yrl");
client.DefaultRequestHeaders.Add("Accept", "application/json");
_logger = logger;
AuthHeader = authHeader;
}
Now as I have registered that as Singleton
. So when call my Api for first time and provide the Authorization value in header the api is called successfully but the issue is when i pass empty Authorization header it still call's api successfully as it is storing old header value due to Singleton. How can I fix this? Is there any otherways to do what I am doing.
Upvotes: 4
Views: 17761
Reputation: 1003
Try using HttpClientFactory
, that was added Asp.Net Core 2.1, in conjunction with HttpMessageHandler
to achieve what you are trying to do.
You can register the HttpClient in ConfigureServices
method
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<BaseClient>(client =>
{
client.BaseAddress = new Uri("yrl");
client.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
}
With the above code in place, your BaseClient
will receive the HttpClient instance via DI.
In order to validate/inspect the AuthHeader
you can configure the HttpMessageHandler
for the registered HttpClient
. The code for the message handler is simple like below:
public class AuthHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("Authorization"))
{
return new HttpResponseMessage(HttpStatusCode.Forbidden)
{
Content = new StringContent("No Authorization header is present")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
In order to register the above handler, your code will look like below:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<AuthHeaderHandler>();
services.AddHttpClient<BaseClient>(client =>
{
//code omitted for brevity
...
})
.AddHttpMessageHandler<AuthHeaderHandler>();
}
You can inject whatever you need inside the message handler if needed. However, no need to inject the IHttpContextAccessor in the BaseClient
. To read more about HttpClientFactory and HttpMessageHandlers please see this link and this. I hope this helps.
UPDATED ANSWER
Please have a look at the more concrete example of HttpMessageHandler that uses the IHttpContextAccessor and modifies the HttpRequestMessage i.e. adds the Authorization header before the call is made. You can modify the logic as per your need.
public class AuthHeaderHandler : DelegatingHandler
{
private readonly HttpContext _httpContext;
public AuthHeaderHandler(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (_httpContext != null)
{
var accessToken = await _httpContext.GetTokenAsync(TokenKeys.Access);
if (!string.IsNullOrEmpty(accessToken))
{
// modify the request header with the new Authorization token
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
}
return await base.SendAsync(request, cancellationToken);
}
}
UPDATED ANSWER 2
Please have a look at the simple solution that I have uploaded to GitHub. The solution is even simpler than I originally suggested. As you are not integrating any identity-based Authentication/Authorization, you can simply use a CustomActionFilter, I called it ValidateAuthHeader
, to check if the AuthHeader is present or not and return the usual 403 if absent.
Within the ValidateAuthHeader
, I have utilised the middleware code that you posted earlier. You can then simply add this attribute on the ActionMethods or Controllers which require this check.
Please have a look at the DataController
and ValuesController
. The DataController
will receive the typed HttpClient
that will be used to call the values
endpoint. ValidateAuthHeader
is present on the GetValues
and will check for the AuthHeader. If it's absent it will generate the error.
[Route("api/[controller]")]
[ApiController]
public class DataController : ControllerBase
{
private readonly MyHttpClient _client;
public DataController(MyHttpClient client)
{
_client = client;
}
[ValidateAuthHeader]
public async Task<IActionResult> GetValues()
{
var response = await _client.GetAsync("api/values");
var contents = await response.Content.ReadAsStringAsync();
return new ContentResult
{
Content = contents,
ContentType = "application/json",
StatusCode = 200
};
}
}
The rest of the flow is the same as I originally suggested. The call will be passed through the AuthHeaderHandler
which is an HttpMessageHandler
for the registered MyHttpClient
. Please have a look at the Startup.cs
.
The handler will retrieve the HttpContext
via HttpContextAccessor
and will check for the AuthHeader
. If present, it will add it to the RequestMessage parameter.
I hope this helps. Feel free to ask any questions that you may have.
Setting Auth Header without using HttpMessageHandler
Modify the MyHttpClient and add a public method called SetAuthHeader
public class MyHttpClient
{
private readonly HttpClient _httpClient;
public MyHttpClient(HttpClient client)
{
_httpClient = client;
}
public void SetAuthHeader(string value)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", value);
}
}
Then call this method in your action method as you will have the AuthHeader in the HttpContext.Request at that point
[ValidateAuthHeader]
public async Task<IActionResult> GetValues()
{
var authHeader = Request.Headers["Authorization"];
_client.SetAuthHeader(authHeader.First());
var response = await _client.GetAsync("api/values");
var contents = await response.Content.ReadAsStringAsync();
return new ContentResult
{
Content = contents,
ContentType = "application/json",
StatusCode = 200
};
}
Remove the AuthHeaderHandler registration and delete the AuthHeaderHandler.
Upvotes: 2