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?
Related
I'm fairly new to Asp.Net core 6 and am working on an GraphQL API that receives a bearer token in the request. The API then invokes another Web API and passes the same bearer token in the header. Below is what my code looks like-
Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<HeaderHandler>();
builder.Services.AddScoped<TokenContainer>();
//builder.Services.AddScoped<IFooGateway, FooGateway>();
builder.Services.AddHttpClient<IFooGateway, FooGateway>((c) =>
{
c.BaseAddress = new Uri(builder.Configuration["FooApiUrl"]);
})
.AddHttpMessageHandler<HeaderHandler>();
builder.Services.AddTransient<GraphApiService>();
var app = builder.Build();
app.UseMiddleware<HeaderMiddleware>();
app.MapGraphQL();
app.Run();
HeaderMiddleware.cs
public class HeaderMiddleware
{
//TokenContainer _tokenContainer;
private readonly RequestDelegate _requestDelegate;
public HeaderMiddleware()
{
}
public HeaderMiddleware(RequestDelegate requestDelegate)
{
_requestDelegate = requestDelegate;
//_tokenContainer = tokenContainer;
}
public async Task InvokeAsync(HttpContext context, TokenContainer tokenContainer)
{
var header = context.Request.Headers.Authorization;
tokenContainer.SetToken(header);
await _requestDelegate(context);
}
TokenContainer.cs:
public class TokenContainer
{
public string BearerToken { get; private set; }
public void SetToken(string token) => BearerToken = token;
}
HeaderHandler.cs:
public class HeaderHandler : DelegatingHandler
{
TokenContainer _tokenContainer;
public HeaderHandler()
{
}
public HeaderHandler(TokenContainer tokenContainer)
{
_tokenContainer = tokenContainer;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// for every request sent via the http client, intercept & add the bearer token header.
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", _tokenContainer.BearerToken);
// continue with request pipeline
return await base.SendAsync(request, cancellationToken);
}
}
FooGateway.cs:
public class FooGateway : IFooGateway
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
private readonly string _context = String.Empty;
public FooGateway(HttpClient httpClient, IConfiguration configuration)
{
_configuration = configuration;
_context = configuration["FooContext"];
_httpClient = httpClient;
}
public void DoSomething()
{
_httpClient.PostAsync("/blabla");
}
}
So, the idea was that the bearer token for every incoming request will be stored in a class called TokenContainer and the HttpHandler will add it to all the outgoing requests.
However, what is happening is that the token is stored in the TokenContainer but the HeaderHandler gets a different instance of TokenContainer in its constructor with its BearerToken property set to null.
Can someone please explain why the same instance of TokenContainer from the middleware is not being passed into the HeaderHandler?
The issue you are seeing is because the lifetime of the HttpMessageHandler is not the same as the lifetime of the request: usually, the same handler will be reused across many requests and be controlled separately on expiration timers and such.
You should not expect that a service injected into your message handler will be the same object that is injected outside it when it is registered as scoped.
https://andrewlock.net/understanding-scopes-with-ihttpclientfactory-message-handlers/#scope-duration-in-ihttpclientfactory
As the article suggests, to use the same scoped instance as you do outside the handler, you have to rely on IHttpContextAccessor to access the current HttpContext and fetch the service from there. So your handler implementation would look something like this:
public class HeaderHandler : DelegatingHandler
{
IHttpContextAccessor _httpContextAccessor;
public HeaderHandler()
{
}
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var tokenContainer = _httpContextAccessor
.HttpContext
.RequestServices
.GetRequiredService<TokenContainer>();
// for every request sent via the http client, intercept & add the bearer token header.
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", tokenContainer.BearerToken);
// continue with request pipeline
return await base.SendAsync(request, cancellationToken);
}
}
This should make sure that the TokenContainer instance is the same across your current request and http calls.
Remember that to add this functionality you need to add the accessor like this:
services.AddHttpContextAccessor();
I'm looking into using IHttpClientFactory for calling external APIs in my asp.net core app. I've seen a few examples where the client factory is created in the constructor of the service's class. Then the methods of that class, call that client factory to generate an instance of HttpClient to make Http requests. Like the following sample code:
public class MyTransientService: IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyTransientService(
IHttpClientFactory clientFactory
)
{
_clientFactory = clientFactory;
}
public async Task<MyData> GetData()
{
//construct the request
var httpClient = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
...
}
}
If the service is registered as transient in startup.cs, wouldn't a new instance of HttpClientFactory be generated each time that the service is called? A new HttpClientFactory per request? So wouldn't the following be a more efficient way to use the factory?
public class MyTransientService: IMyService
{
private readonly HttpClient _client;
public MyTransientService(
HtpClient client
)
{
_client = client;
}
public async Task<MyData> GetData()
{
Uri uri = new Uri(StaticUtils.AddQueryString(url, props));
var response = await _client.SendAsync(request);
...
}
}
I would consider creating the HttpClient yourself bad practice, on self you have control of how many is created. If MyTransientService is transient, you will end up creating a lot of socket connections (one for each instance/request) HttpClient is created to be reused.
Take a look at Typed clients: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient<MyTransientService>();
services.AddControllers();
}
public class MyTransientService: IMyService
{
private readonly HttpClient _client;
public MyTransientService(
HtpClient client
)
{
_client = client;
}
public async Task<MyData> GetData()
{
Uri uri = new Uri(StaticUtils.AddQueryString(url, props));
var response = await _client.SendAsync(request);
...
}
}
I have created an Blazor WebAssembly project and want to provide a WebAPI with one public available function.
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class SystemEvalApiController : ControllerBase
{
public SystemEvalApiController(AppDbContext context, IMapper mapper)
{...}
[Route("LatestEvals")]
[AllowAnonymous]
public ActionResult LatestEvals()
that is my Api controller and I should be able to call it with:
SystemEvalPublicViewModel = await Http
.GetFromJsonAsync<SystemEvalPublicViewModel>(
HttpService.BuildUrl("api/SystemEvalApi/LatestEvals"));
When i am not logged into any account. But instead I get this error:
info: System.Net.Http.HttpClient.JPB.BorannRemapping.ServerAPI.LogicalHandler[100]
Start processing HTTP request GET https://localhost:44330/api/SystemEvalApi/LatestEvals
blazor.webassembly.js:1 info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed.
It looks like the "DefaultAuthorizationService" does not recognize the Anonymous attribute but I cannot find the point where it fails directly.
How do I declare an WebAPI function to be accessable from the HttpClient without Login.
Microsoft.AspNetCore.Components.WebAssembly.Server 3.2.0.-rc1.20223.4
Edit:
Here is the declaration for ClientServices:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddHttpClient("JPB.BorannRemapping.ServerAPI", client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
})
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("JPB.BorannRemapping.ServerAPI"));
builder.Services.AddTransient(e => new HttpService(e.GetService<HttpClient>()));
builder.Services.AddApiAuthorization();
builder.Services.AddBlazoredLocalStorage();
await builder.Build().RunAsync();
So each time you acquire an HttpClient it use the BaseAddressAuthorizationMessageHandler which try to authentify the request. But it this case your request should not be authentified, so you can make something like :
Registration
builder.Services.AddHttpClient("JPB.BorannRemapping.ServerAPI.Anonymous", client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
});
Usage
#inject IHttpClientFactory _factory
#code {
...
var httpClient = _factory.CreateClient("JPB.BorannRemapping.ServerAPI.Anonymous");
var httpService = new HttpService(httpClient);
SystemEvalPublicViewModel = await httpClient
.GetFromJsonAsync<SystemEvalPublicViewModel>(
httpService.BuildUrl("api/SystemEvalApi/LatestEvals"));
}
Building on the answer from #agua from mars.
Registration in Program.cs
You could add 2 named HttpClient to the services collection (the first for authenticated calls the second for anonymous):
builder.Services.AddHttpClient("YourProject.ServerAPI",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddHttpClient("YourProject.ServerAPI.Anonymous",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("YourProject.ServerAPI"));
//Register a new service for getting an Anonymous HttpClient
builder.Services.AddScoped<IHttpAnonymousClientFactory, HttpAnonymousClientFactory>();
Add new Interface and Implementation for Dependency Injection:
public interface IHttpAnonymousClientFactory
{
HttpClient HttpClient { get; }
}
public class HttpAnonymousClientFactory : IHttpAnonymousClientFactory
{
private readonly IHttpClientFactory httpClientFactory;
public HttpAnonymousClientFactory(IHttpClientFactory httpClientFactory)
{
this.httpClientFactory = httpClientFactory;
}
public HttpClient HttpClient => httpClientFactory.CreateClient("YourProject.ServerAPI.Anonymous");
}
Usage in Razor Component (for Anonymous HttpClient)
[Inject]
private IHttpAnonymousClientFactory httpAnonymousClientFactory { get; set; }
private MyViewModel myModel;
protected override async Task OnInitializedAsync()
{
myModel = await httpAnonymousClientFactory.HttpClient.GetFromJsonAsync<MyViewModel>($"api/mycontroller/myendpoint");
}
Usage in Razor Component (for Authenticated HttpClient)
[Inject]
private HttpClient httpClient { get; set; }
private MyOtherViewModel myModel;
protected override async Task OnInitializedAsync()
{
myModel = await httpClient.GetFromJsonAsync<MyOtherViewModel>($"api/mycontroller/mysecureendpoint");
}
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 should I use HttpClientFactory to return an instance of HttpClient whose uri and credentials are determined at the point of the call?
The existing code looks like this:
var httpClientHandler = new HttpClientHandler()
{
Credentials = new NetworkCredential(userName, password),
};
HttpClient client = new HttpClient(httpClientHandler);
client.BaseAddress = new Uri(_appSetting.ServiceURI);
your ConfigureServices method in Start up class
services.AddHttpClient("github", c =>
{
//c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
}).ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseDefaultCredentials = true,
Credentials = new NetworkCredential("", ""),
};
});
Your Controller will look like this
private readonly IHttpClientFactory _httpClientFactory;
public DataProController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
[HttpGet]
public async Task<ActionResult> Get()
{
var client = _httpClientFactory.CreateClient("github");
client.BaseAddress = new Uri("https://api.github.com/");
string result = await client.GetStringAsync("/");
return Ok(result);
}
You may not be able to set up Network Credentials at the run time when using httpclientfactory and may need to setup up in the startup class. you can find about this issue here.
https://github.com/aspnet/HttpClientFactory/issues/71
You can create an authentication delegating handler like this:
public class AuthenticationHttpMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Get the token or other type of credentials here
// string scheme = ... // E.g. "Bearer", "Basic" etc.
// string credentials = ... // E.g. formatted token, user/password etc.
request.Headers.Authorization =
new AuthenticationHeaderValue(scheme, credentials);
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
}
then add it to the HttpClient builder and to the DI container:
services
.AddTransient<AuthenticationHttpMessageHandler>()
.AddHttpClient("MyClient")
.AddHttpMessageHandler<AuthenticationHttpMessageHandler>();
then use IHttpClientFactory to create HttpClient instances.
The core advantage of this approach is that you clearly separate concerns. You don't touch the primary handler, you don't manage client creation manually, you utilize the whole power of the factory and its builder extension methods. The authentication handler is naturally injected in the pipeline and adds authorization to each request. This handler can be enhanced further by abstracting away the source of credentials and make the handler depend on some IAuthenticationProvider abstraction, which will require only DI configuration and not touching the HttpClient configuration code.
If you using the .net Dependency Injection you can add the configuration for one class into your setup code:
services
.AddTransient<DataLoader>()
.AddHttpClient<DataLoader>().ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
{
Credentials = new NetworkCredential(LoadUsernameFromConfig(), LoadPasswordFromSecureLocation())
});
Add now the DI will inject a HttpClient that uses this credential into the DataLoader class:
public class DataLoader
{
private readonly HttpClient httpClient;
public DataLoader(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public async Task LoadData(string tableName)
{
var json = await httpClient.GetStringAsync("https://protected.example.com/json");
...
}
}
(I would not be able to come up with this code if I had not the answer from Imran Arshad: Thanks!)