Add multiple tokens into DefaultRequestHeaders.Authorization in HttpClient - c#

The API I'm calling from my ASP.NET Web API app requires two tokens i.e. accessToken and userToken.
The following code is not working because it takes only the second token, not both. Looks like the second line is over-writing the first one.
How do I add multiple tokens to my request header?
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("APIAccessToken", "token1");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("UserToken", "token2");
UPDATE:
Here's the way I set this up and it's not working. Basically, my API calls seem to go nowhere. I get no errors. Just no response.
First, I have the HttpClientAccessor that looks like this:
public static class HttpClientAccessor
{
private static Lazy<HttpClient> client = new Lazy<HttpClient>(() => new HttpClient());
public static HttpClient HttpClient
{
get
{
client.Value.BaseAddress = new Uri("https://api.someurl.com");
client.Value.DefaultRequestHeaders.Accept.Clear();
client.Value.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Value.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.Value.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client.Value;
}
}
}
I then have my ApiClient that will perform my API calls which looks like this:
public class MyApiClient
{
HttpClient _client;
public MyApiClient()
{
_client = HttpClientAccessor.HttpClient;
}
public async Task Get()
{
try
{
HttpResponseMessage response = await _client.GetAsync("/myendpoint"); // This is where it gets lost
var data = await response.Content.ReadAsStringAsync();
}
catch(Exception e)
{
var error = e.Message;
}
}
}
This is my controller action:
public class MyController : Controller
{
private readonly MyApiClient _client;
public MyController()
{
_client = new MyApiClient();
}
public IActionResult SomeAction()
{
_client.Get().Wait();
}
}

You are confusing the standard authorization header with custom headers
According to the linked documentation
Request Header
Add the generated tokens to the request headers "APIAccessToken" and "UserToken"
Example Request
APIAccessToken: zjhVgRIvcZItU8sCNjLn+0V56bJR8UOKOTDYeLTa43eQX9eynX90QntWtINDjLaRjAyOPgrWdrGK12xPaOdDZQ==
UserToken: 5sb8Wf94B0g3n4RGOqkBdPfX+wr2pmBTegIK73S3h7uL8EzU6cjsnJ0+B6vt5iqn0q+jkZgN+gMRU4Y5+2AaXw==
To get headers like above, add them to the client like below
_client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
_client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
Based on shown update, the client is adding the headers every time the client is called. This should be in the value factory of the lazy client.
public static class HttpClientAccessor {
public static Func<HttpClient> ValueFactory = () => {
var client = new HttpClient();
client.BaseAddress = new Uri("https://someApiUrl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client;
};
private static Lazy<HttpClient> client = new Lazy<HttpClient>(ValueFactory);
public static HttpClient HttpClient {
get {
return client.Value;
}
}
}
The controller action also needs to be refactored to avoid deadlocks because of the mixing of async and blocking calls like .Wait() or .Result.
public class MyController : Controller {
private readonly MyApiClient _client;
public MyController() {
_client = new MyApiClient();
}
public async Task<IActionResult> SomeAction() {
await _client.Get();
//... code removed for brevity
}
}

Related

getting 401 unauthorize while using IdentityModel.AspNetCore -1.0.0-rc.4.1

I am trying to access a protected API using client credential flow in my asp.net core 3.1 application.
For token management I am using IdentityModel.AspNetCore -1.0.0-rc.4.1.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<ApiService>(client =>
{
client.BaseAddress = new Uri("http://localhost:10811/");
})
.AddClientAccessTokenHandler();
services.AddAccessTokenManagement(options =>
{
options.Client.Clients.Add("auth", new ClientCredentialsTokenRequest
{
Address = "http://localhost:10811/token",
ClientId = "client1",
ClientSecret = "Supersecret"
});
});
}
I am always getting 401 while trying to access the protected API service.
ApiService code,
public class ApiService
{
public HttpClient HttpClient;
public ApiService(HttpClient client)
{
HttpClient = client;
}
public async Task<string> GetContactsAsync()
{
var response = await HttpClient.GetAsync("http://localhost:10811/test");
response.EnsureSuccessStatusCode();
return "Done";
}
}
And here I am calling
public class MyCallService
{
private readonly IHttpClientFactory _clientFactory;
public MyCallService(IHttpClientFactory clientFactory)
{
if (clientFactory != null)
_clientFactory = clientFactory;
}
public void Call()
{
var client = _clientFactory.CreateClient();
var apiService= new ApiService(client);
await apiService.GetContactsAsync();
}
}
Is the above code setting any token, what I am missing here? Where to put Bearer token in the authorization header.
In order to send the token with any request from the httpclient , you need to inject it before and to do that you need to use AddClientAccessTokenClient method under the AddAccessTokenManagement
services.AddClientAccessTokenClient("client", configureClient: client =>
{
client.BaseAddress = new Uri("http://localhost:10811/");
});
and you need to specifiy the name of the config to use in order to create httpclient
_client = factory.CreateClient("client");
and now you can simply call
var response = await HttpClient.GetAsync("test"); //no need to specify the full URL

How can I add a query string into HttpClient.BaseAdress in c#?

I'm trying to pass a query string into a BaseAddress but it doesn't recognize the quotation mark "?".
The quotation breaks the URI
First I create my BaseAddress
httpClient.BaseAddress = new Uri($"https://api.openweathermap.org/data/2.5/weather?appid={Key}/");
Then I call the GetAsync method, trying to add another parameter
using (var response = await ApiHelper.httpClient.GetAsync("&q=mexico"))....
This is the URI the code is calling
https://api.openweathermap.org/data/2.5/&q=mexico
I'd be tempted to use a DelegatingHandler if you need to apply an API key to every single request:
private class KeyHandler : DelegatingHandler
{
private readonly string _escapedKey;
public KeyHandler(string key) : this(new HttpClientHandler(), key)
{
}
public KeyHandler(HttpMessageHandler innerHandler, string key) : base(innerHandler)
{
// escape the key since it might contain invalid characters
_escapedKey = Uri.EscapeDataString(key);
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// we'll use the UriBuilder to parse and modify the url
var uriBuilder = new UriBuilder(request.RequestUri);
// when the query string is empty, we simply want to set the appid query parameter
if (string.IsNullOrEmpty(uriBuilder.Query))
{
uriBuilder.Query = $"appid={_escapedKey}";
}
// otherwise we want to append it
else
{
uriBuilder.Query = $"{uriBuilder.Query}&appid={_escapedKey}";
}
// replace the uri in the request object
request.RequestUri = uriBuilder.Uri;
// make the request as normal
return base.SendAsync(request, cancellationToken);
}
}
Usage:
httpClient = new HttpClient(new KeyHandler(Key));
httpClient.BaseAddress = new Uri($"https://api.openweathermap.org/data/2.5/weather");
// since the logic of adding/appending the appid is done based on what's in
// the query string, you can simply write `?q=mexico` here, instead of `&q=mexico`
using (var response = await ApiHelper.httpClient.GetAsync("?q=mexico"))
** Note: If you're using ASP.NET Core, you should call services.AddHttpClient() and then use IHttpHandlerFactory to generate the inner handler for KeyHandler.
This is how I work around it:
Http client impl:
namespace StocksApi2.httpClients
{
public interface IAlphavantageClient
{
Task<string> GetSymboleDetailes(string queryToAppend);
}
public class AlphavantageClient : IAlphavantageClient
{
private readonly HttpClient _client;
public AlphavantageClient(HttpClient httpClient)
{
httpClient.BaseAddress = new Uri("https://www.alphavantage.co/query?apikey=<REPLACE WITH YOUR TOKEN>&");
httpClient.DefaultRequestHeaders.Add("Accept", "application/json; charset=utf-8");
httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
_client = httpClient;
}
public async Task<string> GetSymboleDetailes(string queryToAppend)
{
_client.BaseAddress = new Uri(_client.BaseAddress + queryToAppend);
return await _client.GetStringAsync("");
}
}
}
Controller:
namespace StocksApi2.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class SymbolDetailsController : ControllerBase
{
private readonly IAlphavantageClient _client;
public SymbolDetailsController(IAlphavantageClient client)
{
_client = client;
}
[HttpGet]
public async Task<ActionResult> Get([FromQuery]string function = "TIME_SERIES_INTRADAY",
[FromQuery]string symbol = "MSFT", [FromQuery]string interval = "5min")
{
try {
string query = $"function={function}&symbol={symbol}&interval={interval}";
string result = await _client.GetSymboleDetailes(query);
return Ok(result);
}catch(Exception e)
{
return NotFound("Error: " + e);
}
}
}
}
And in Startup.cs inside ConfigureServices:
services.AddHttpClient();
services.AddHttpClient<IAlphavantageClient, AlphavantageClient>();

HttpContext Header

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.
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.

Set a default header to each http client call

I want to set a default header for every method in the UserHttpClient but I don`t want that every method is doing that, I want to do it in a general way.
The problem I see with the current implementation is, that when I call one method the _client gets disposed thus at the next call within a Http Request the _client is not initialized, as this happens within the constructor.
The UserHttpClient is registered via DI as per Http Request.
I also do not want to create a private/base method where I pass the _client and do the header addition there.
How would you solve that problem?
public class UserHttpClient : IUserRemoteRepository
{
private readonly string baseUrl = ConfigurationManager.AppSettings["baseUrl"];
private readonly string header = ConfigurationManager.AppSettings["userHeader"];
private readonly HttpClient _client;
public ServiceProductDataProvider(string toolSystemKeyHeader)
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add(header, token);
}
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
using (_client)
{
// do stuff
var users = await _client.GetAsync("url here");
}
}
public async Task<UserDto> GetUser(Guid userId)
{
using (_client)
{
// do stuff
var users = await _client.GetAsync("url here");
}
}
}
The class UserHttpClient has a member that is IDisposable (private readonly HttpClient _client;). That means that the UserHttpClient should also implement IDisposable:
public void Dispose()
{
_client.Dispose();
}
Then, the class/code that is using UserHttpClient is responsible for Disposing it after it's done with it. If the instance is injected, then the DI framework you use probably handles disposing it automatically at the end of the request. What's left for you then is to simply remove the using blocks from the implementation:
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
// do stuff
var users = await _client.GetAsync("url here");
}
---- EDIT ----
You could also work around the issue by not reusing the HttpClient:
private string _toolSystemKeyHeader;
public ServiceProductDataProvider(string toolSystemKeyHeader)
{
_toolSystemKeyHeader = toolSystemKeyHeader
}
private HttpClient GetClientInstance()
{
HttpClient _client = new HttpClient();
_client.DefaultRequestHeaders.Add(header, _toolSystemKeyHeader); //?? in your original code, the toolSystemKeyHeader is not used, but I guess it is the token..?
return _client;
}
And:
public async Task<List<UserDto>> GetUsers(UserRequestDto dto)
{
using (var _client = GetClientInstance())
{
// do stuff
var users = await _client.GetAsync("url here");
}
}

Calling remote web api from mvc controller

What is the preferred way for handling web api endpoints for each controller?
For example, my MVC controller will be calling different endpoints.
These are the only ones for now, but it could change.
Also, I will be developing this locally and and deploying to development server.
http://localhost:42769/api/categories/1/products
http://localhost:42769/api/products/
public class ProductsController : Controller
{
HttpClient client;
string url = "http://localhost:42769/api/categories/1/products"; //api/categories/{categoryId}/products
public ProductsController()
{
client = new HttpClient();
client.BaseAddress = new Uri(url);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
// GET: Products
public async Task<ActionResult> ProductsByCategory()
{
HttpResponseMessage responseMessage = await client.GetAsync(url);
if (responseMessage.IsSuccessStatusCode)
{
var responseData = responseMessage.Content.ReadAsStringAsync().Result;
var products = JsonConvert.DeserializeObject<List<GetProductsByCategoryID>>(responseData);
return PartialView(products);
}
return View("Error");
}
}
Not sure I understand you question or problem, but I would create a wrapper class for the service and then have different methods for each resource that you need to call. Always think SOLID.
Example (written by hand)
public class Client
{
private Uri baseAddress;
public Client(Uri baseAddress)
{
this.baseAddress = baseAddress;
}
public IEnumerable<Products> GetProductsFromCategory(int categoryId)
{
return Get<IEnumerable<Product>>($"api/categories/{categoryId}/products");
}
public IEnumerable<Products> GetAllProducts()
{
return Get<IEnumerable<Product>>($"api/products");
}
private T Get<T>(string query)
{
using(var httpClient = new HttpClient())
{
httpClient.BaseAddress = baseAddress;
var response= httpClient.Get(query).Result;
return response.Content.ReadAsAsync<T>().Result;
}
}
}

Categories

Resources