4

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.

Manoj Choudhari
  • 5,277
  • 2
  • 26
  • 37
Shabir jan
  • 2,295
  • 2
  • 23
  • 37

1 Answers1

2

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.

Shahzad Hassan
  • 993
  • 7
  • 14
  • thanks for your answer but thats not What I am looking for. `AuthenticationHeader` is used to get the Header and than I am injecting that class so I can access AuthHeader while making authorized requests. – Shabir jan Apr 23 '19 at 15:50
  • 1
    @Shabirjan, actually Hassan is right. If the only purpose of AuthenticationHeader is storing original Authorization header and BaseClient uses the same header to make its requests you can inject IHttpContextAccessor in AuthHeaderHandler proposed by him, and set Authorization Header there. And inject HttpClient instead of BaseClient. If it's not clear what I say I can add new answer with code example. But I think Hassan can edit his answer to give you more correct answer. – Artur Apr 24 '19 at 07:54
  • @Artur As per Hassan answer I should be using HTTPClientFactory which I am already doing (using typed client). And the purpose of `AuthenticationHeader` is storing the Authorization Header from Request and afterwards I am Injecting that in my `BaseClient` where I access authToken and made request by adding that AuthHeader to my request. – Shabir jan Apr 24 '19 at 08:02
  • @Artur the purpose of `AuthenticationHeader` is not to check whether the request contains AuthHeader or not. Instead its purpose to get AuthorizationHeader from request and save it. – Shabir jan Apr 24 '19 at 08:03
  • @Artur Just to update, I am using `MiddleWare` for checking AuthorizationHeader in request, but also using MiddleWarePipeline as some request should be accessible without Authorization Header. – Shabir jan Apr 24 '19 at 08:09
  • @Shabirjan The purpose of the HttpMessageHandler is not just to check if the header is present, but you can also use it add it to the request before you make a call to the SendAsync method of `HttpClient`. Think of HttpMessageHandler as a Middleware for HttpClient. You can have multiple HttpMessageHandler for the same client. As I mentioned, you can inject the IHttpContextAccessor into the `HttpMessageHandler` then check if the Authorization header is present, if it is then add it to the HttpRequestMessage parameter. – Shahzad Hassan Apr 24 '19 at 08:31
  • See the example of multiple handlers here https://www.stevejgordon.co.uk/httpclientfactory-aspnetcore-outgoing-request-middleware-pipeline-delegatinghandlers – Shahzad Hassan Apr 24 '19 at 08:32
  • 1
    Please have a look at my updated answer. Thanks, @Artur for the suggestion. – Shahzad Hassan Apr 24 '19 at 08:40
  • @Hassan thanks , but what if i dont want to send Authorization header to some request which don;t required that header? how should i will make sure of this case? Right now i am using Middlewarepipeline too ensure this. – Shabir jan Apr 24 '19 at 08:41
  • It seems that you know which requests don't need the Auth header. In that case, you can add the other header to the HttpRequestMessage that is utilised by your HttpClient. Then in the HttpMessageHandler, you can check if the HttpRequestMessage parameter contains a header, say auth-not-required, or something more cryptic, then don't add the Authorization header. – Shahzad Hassan Apr 24 '19 at 08:45
  • What if i don't want to add extra header in my request, because right now my Middleware way is working absolutely fine without adding more headers in my request. this is how i make sure which requests should be passed through that Middleware `[MiddlewareFilter(typeof(AuthorizationMiddlewarePipeline))]` – Shabir jan Apr 24 '19 at 08:52
  • Well, in that case, it's simple, you can simply check in `HttpMessageHandler` using the IHttpContextAccessor that AuthHeader is not present, then don't modify the HttpRequestMessage. As you are using `AuthorizationMiddlewarePipeline` to the specific requests. In my updated answer I am using `var accessToken = await _httpContext.GetTokenAsync(TokenKeys.Access);` you can change it to check for `if (_httpContext.Request.Headers.Contains("Authorization")) { // add auth to the request message }` – Shahzad Hassan Apr 24 '19 at 09:02
  • But what if i am trying to call a service which requires Authorization Header and I am not sending it, it will pass isn't? – Shabir jan Apr 24 '19 at 09:10
  • So you have some services that require the AuthHeader and some that don't. You are using the Middleware for the calls which don't require the header. You can probably change it to use for the calls, which do require the header. So if you don't pass it, it will be handled there. And in the HttpMessageHandler, your code will remain the same, that if AuthHeader is present, add it to the `HttpRequestMessage`. So, if you do pass the AuthHeader to the calls which don't require it, it will be ignored anyway. – Shahzad Hassan Apr 24 '19 at 09:23
  • So you are saying that I should use both Middleware (for checking AuthorizationToken) and HttpMessageHandler for adding AuthorizationToken in my requests? – Shabir jan Apr 24 '19 at 09:51
  • BTW `_httpContext.Request.Headers.Contains("Authorization")` this is not working – Shabir jan Apr 24 '19 at 10:11
  • How are you registering the IHttpContextAccessor? Do you have the line `services.AddHttpContextAccessor()` in `ConfigureServices` method?. I personally think that you can implement a custom Authorize attribute to check if the AuthHeader is present or not, instead of using the Middleware – Shahzad Hassan Apr 24 '19 at 10:21
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/192305/discussion-between-shabir-jan-and-shahzad-hassan). – Shabir jan Apr 24 '19 at 10:37
  • Hi @ShahzadHassan Wanted to point out an issue in the implementation of AuthHeaderHandler. I dont know why but its storing the authorization header value for i think 2 min. I add correct header and the api works , than i add header value which shouldn't be accepted, but it is being accepted. – Shabir jan Apr 29 '19 at 09:42
  • Hi @Shabirjan as per the [documentation](https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#httpclient-lifetimes): "The HttpMessageHandler objects in the pool have a lifetime that's the length of time that an HttpMessageHandler instance in the pool can be reused. The default value is two minutes, but it can be overridden per Typed Client. To override it, call SetHandlerLifetime() on the IHttpClientBuilder that's returned when creating the client, as shown in the following code" – Shahzad Hassan Apr 29 '19 at 11:31
  • Set lifetime for the HttpMessageHandler objects in the pool used for the Typed Client. You can set to lower value ```services.AddHttpClient() .SetHandlerLifetime(TimeSpan.FromMinutes(5));``` – Shahzad Hassan Apr 29 '19 at 11:33
  • @ShahzadHassan I know about this, I was just pointing out that using `AuthHeaderHandler.cs` may not be good solution for adding authorization header in my request. – Shabir jan Apr 29 '19 at 11:58
  • The benefit of using the MessageHandler is that you can intercept the request and do something with it before it is sent. You also have the option to control the lifetime of the handler. If you think this is not working for you, you can set the Authorization header when you receive the MyHttpClient instance inside the controller. You would need to expose the method or property to do that. See my updated answer – Shahzad Hassan Apr 29 '19 at 13:20
  • @Shabirjan out of curiosity, if you are passing in an invalid value in the `Authorization` header, then the `ValidateAuthHeader` attribute, or if you have implemented a `CustomAuthorizationPolicy` as per this [answer](https://stackoverflow.com/a/55898227/5243697), should check if it's a valid value or not and result in 403. It's not the responsibility of AuthHeaderHandler to validate the value. So it's not necessarily the AuthHeaderHandler problem. Even if you don't use AuthHeaderHandler, HttpClient uses a default ClientMessageHandler, so you may see the same behaviour even without the handler – Shahzad Hassan Apr 29 '19 at 20:58
  • @ShahzadHassan As we discussed earlier, I am not validating the Authorization token at my end, but the actual issue is not about validating authorization token, the issue is when I send correct Authorization header, and than send wrong one, the ```AuthHeaderHandler``` somehow uses the old value i.e the Correct Header. That's the main issue with ```AuthHeaderHandler```. BTW I am using `ScopedService` and its working absolutely fine, I am talking about ```AutheticationHeader``` class – Shabir jan Apr 29 '19 at 21:03
  • @ShahzadHassan To give you more overview about the issue i faced was that. 1: Called request let assume getMember and passed Authorization Header ```abc123```, the header was passed to request and i got correct response 2: called same service again and passed Authorization Header ```abc345```, now this header is wrong, but when i passed that value, the ```AuthHeaderHandler``` passed ```abc123``` instead of ```abc345```. – Shabir jan Apr 29 '19 at 21:07
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/192597/discussion-between-shahzad-hassan-and-shabir-jan). – Shahzad Hassan Apr 29 '19 at 21:08