We've got 20 microservices that are all making use of a client library.
This library simply provides a way for the microservice to connect to its destination: CRM Dynamics.
Within each service we have a wrapper that calls this library:
public static class Client
{
public static HttpClient Instance;
static Client()
{
Instance = new TheCrmClient<AdfsAuthenticationHelper>(MyConfigSettings).HttpClient;
}
}
Within each service we use the client like so:
//Issue request
var response = Client.Instance.SendAsync(request).Result;
After the ADFS session has timed out, we will get a 401 response.
How do we force re-authentication when receiving a 401 response?
The authentication occurs in GetHttpsClient():
public class TheCrmClient<T> where T : IAdfsAuthenticator
{
private readonly RestCRMClientConfiguration _configuration;
private IAdfsAuthenticator _authenticator;
private HttpClient _https;
public TheCrmClient(RestCRMClientConfiguration configuration)
{
_configuration = configuration;
}
public HttpClient HttpClient => GetClientHttps();
private void InitializeAuthenticator(HttpClient client)
{
_authenticator = Activator.CreateInstance(typeof(T), client) as IAdfsAuthenticator;
}
private HttpClient GetClientHttps()
{
var clientHandler = new HttpClientHandler
{
UseCookies = true,
CookieContainer = new CookieContainer(),
AllowAutoRedirect = false
};
_https = new HttpClient(clientHandler)
{
BaseAddress = _configuration.GetServiceUri(RestCRMClientConfiguration.UrlProtocolType.Https)
};
if (_authenticator == null)
InitializeAuthenticator(_https);
_authenticator.Authenticate(_configuration.GetNetworkCredential(),
_configuration.GetAuthenticationTestEndpointUrl(RestCRMClientConfiguration.UrlProtocolType.Https));
return _https;
}
}
To clarify the lifecycle of the request is:
Microservice.Controller --> Microservice.ClientWrapper --> TheCrmClientLibrary --> CrmDestination
Related
I try to use service.AddHttpClient to create HttpClient and DI into my other services, but HttpClient need to be set credential informations.
Here is my step,
Startup.cs
services.AddHttpClient("ServiceOne", x => {})
.ConfigurePrimaryHttpMessageHandler(() =>
{ var handler = new HttpClientHandler(){
UseDefaultCredentials = true,
Credentials = new NetworkCredential("account", "password")
};
return handler;
});
2.ServiceOne.cs
public class ServiceOne : IService
{
private readonly IHttpClientFactory _clientFactory;
}
public ServiceOne (IHttpClientFactory clientFactory)
{
this._clientFactory = clientFactory;
}
public string SomeFunction ()
{
var request = new HttpRequestMessage(HttpMethod.Get,"http://uri");
var client = _clientFactory.CreateClient("ServiceOne");
var response = client.SendAsync(request).GetAwaiter().GetResult();
}
I received 401 Unauthorized, it seems the HttpClientHandler not be set correct.
Hope someone could help me this question, Thanks.
Everything works fine as long as you don't use our application for more than 55 minutes after logging in without refresh. After that the customer is unable to communicate with our api's. I have it configured like this:
Portal Azure sign in flow:
Screenshot sign in flow properties
Program.cs
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://test.onmicrosoft.com/61111111e-1111-1111-bbbb-111111111111/API.access");
}).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, UserFactory>();
builder.Services.AddHttpClient("Test.Api", client => client.BaseAddress = new Uri(baseUrl)).AddHttpMessageHandler<TestAuthorizationMessageHandler>();
builder.Services.AddHttpClient<CustomerOrdersClient>(client => client.BaseAddress = new Uri(baseUrl)).AddHttpMessageHandler<TetsAuthorizationMessageHandler>();
AuthorizationMessageHandler:
public class TestaAuthorizationMessageHandler : AuthorizationMessageHandler
{
public TestAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigationManager) : base(provider, navigationManager)
{
ConfigureHandler(authorizedUrls: new[] { "https://localhost:5001", "https://localhost:44360", "https://app.test.nl", "https://test-api-prod-aa.azurewebsites.net" });
}
}
The client I use for this test. It works fine as long as the accesstoken doesnt expire.
public class ShipmentsClient : IClient<Shipment>
{
private readonly HttpClient _client;
private readonly string _endpoint = " api/shipments";
public ShipmentsClient(HttpClient client)
{
_client = client;
}
public async Task<List<Shipment>> GetAsync()
{
var shipments = new List<Shipment>();
try
{
shipments = await _client.GetFromJsonAsync<List<Shipment>>(_endpoint);
}
catch(Exception ex)
{
shipments = new List<Shipment>();
}
return shipments;
}
}
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
I'm relatively new to using HttpClient and API's but I've done some searching around and it seems that the general consensus even from microsoft docs is that HttpClient should be instantiated once. I have multiple places where I need to make calls to an API endpoint in my project so I made an HttpClient utility class that can be called.
public static class HttpClientManager
{
private static HttpClient _httpClient;
private static HttpClientHandler _handler;
private static readonly string _apiBaseUrl = ConfigurationManager.AppSettings["ApiUrl"];
static HttpClientManager()
{
if (_handler == null) _handler = new HttpClientHandler
{
UseDefaultCredentials = true,
PreAuthenticate = true,
AllowAutoRedirect = true,
ClientCertificateOptions = ClientCertificateOption.Automatic
};
if (_httpClient == null) _httpClient = new HttpClient(_handler, false);
}
public static HttpClient GetHttpClient()
{
if(_httpClient.BaseAddress == null)
{
_httpClient.BaseAddress = new Uri(_apiBaseUrl);
}
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return _httpClient;
}
}
The general idea is that the static constructor initializes the http client and then I have a static method GetHttpClient that will set the address and headers and return the client back which other classes in my project can then make a call like so HttpClientManager.GetHttpClient().GetAsync(.....)....
In theory this should ensure that the client gets initialized once on the first call in the static constructor and should re-use it throughout the life of the application. Is this the proper way of doing something like this or are there better designs?
Any advice would be appreciated, thanks!
I am adding claims transformation into my Blazor (server-side) application. I am creating an HTTP Web API service using DI. Below is the start up code.
services.AddHttpClient<IAPIClient, APIClient>();
services.AddScoped<IClaimsTransformation, ClaimsLoader>();
I would like to then use claims transformation to call this Web API once I am authenticated. Which looks like:
public class ClaimsLoader : IClaimsTransformation
{
private readonly HttpClient _apiClient;
private readonly IHttpContextAccessor _httpAccessor;
public ClaimsLoader(IHttpContextAccessor httpAccessor, HttpClient apiClient)
{
_httpAccessor = httpAccessor;
_apiClient = apiClient;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (ClaimsIdentity)principal.Identity;
var claimsIdentity = new ClaimsIdentity(
identity.Claims,
identity.AuthenticationType,
identity.NameClaimType,
identity.RoleClaimType);
...claims Web API call
return new ClaimsPrincipal(claimsIdentity);
}
}
This is the Web API client setup:
public class APIClient : IAPIClient
{
private readonly HttpClient _httpClient;
public APIClient(IHttpContextAccessor httpAccessor, HttpClient client, IConfiguration configuration)
{
var accessToken = httpAccessor.HttpContext.GetTokenAsync("access_token").Result;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.BaseAddress = new Uri(configuration["Api_Location"]);
_httpClient = client;
}
....
}
The problem arises because I don't understand DI that well. I want to use the APIClient that I created before it but, I am not sure how to pass that into the ClaimsLoader. I assume that it would be something like:
private readonly IHttpContextAccessor _httpAccessor;
private readonly IAPIClient _apiClient;
public ClaimsLoader(IHttpContextAccessor httpAccessor, IAPIClient apiClient)
{
_httpAccessor = httpAccessor;
_apiClient = apiClient;
}
But trying this cause the app to hang when starting. What am I missing? Ihave the API call create and working so that it will return a list of claims.
UPDATE 10/2/2019
I think I have found the issue is related to an infinite loop caused by the following line:
var accessToken = _httpAccessor.HttpContext.GetTokenAsync("access_token").Result;
This causes a call to AuthenticateAsync which in turn then calls this line again. Is there a way to get the Bearer token differently so as not to cause this loop?