I have a regular ASP.Net Core web site that users access using Windows Authentication to determine which users can access which pages.
In order to render a page for the user, the site needs to call in to a series of web services to fetch various bits of data. These web services don't use Windows Authentication. Instead, they require the user's JWT Token.
So, our WebSite needs to exchange the user's Windows token for a JWT token. We have a special ExchangeToken web service that accepts a request using Windows Authentication, and returns the user's JWT Token.
The difficulty comes when I want WebSite to call this ExchangeToken web service. I need to call it using Impersonation, so that I get the user's JWT Token back. However, it doesn't appear to be possible to use HttpClient with Impersonation.
Initially, I had planned to do this in WebSite:
Repeatedly...
Impersonate the user
Instantiate an HttpClient
Call the TokenExchange service to get the JWT Token
Dispose the HttpClient
Stop impersonation
Return the token
However, according to what I've read, re-creating an HTTP client for every call is bad practice, and I should be using HttpClientFactory instead.
However, I don't see how this approach can work with Impersonation.
I tried this:
Use HttpClientFactory to create an HttpClient
Repeatedly...
Impersonate the user
Call the TokenExchange service to get the JWT Token
Stop impersonation
Return the token
However, what happens is that, despite the impersonation, all calls to the TokenExchange service are made with the same windows credentials - the credentials of the user who happens to access the web site first. AFAIK, this stems from the way that Windows Authentication works - it performs a token exchange the first time you use an HttpClient, and from then on, all calls for that client use the same token.
One option would be to create a separate client for each user... but I have about 7,000 users, so that seems a bit excessive!
Another option would be to trust the WebSite to fetch the tokens on behalf of the user, using its own account. The problem with this is that it entails trusting the WebSite. If it is compromised by an attacker, then I can't stop the attacker stealing JWT tokens for arbitrary user. Whereas, with the impersonation, the attacker still can't get a user's JWT token without first obtaining their Windows token.
So, is there a way to do impersonation + IHttpClientFactory together? Or is there a better way to approach all this?
(If it matters, my company has its own Windows servers - we're not in the cloud, yet)
To demonstrate the problem with the second approach, I made a test application. It doesn't actually use HttpClientFactory, but it does demonstrate the problem.
I started with a web site that just returns the user who made a call:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class WhoController : ControllerBase
{
[HttpGet]
public ActionResult<string> Get()
{
return User.Identity.Name;
}
}
My client code works like this:
private void CallClient(HttpClient httpClient, string username, string password)
{
LogonUser(username, "MYDOMAIN", password, 2, 0, out IntPtr token);
var accessTokenHandle = new SafeAccessTokenHandle(token);
WindowsIdentity.RunImpersonated(
accessTokenHandle,
() =>
{
string result = httpClient.GetStringAsync("http://MyServer/api/who").Result;
Console.WriteLine(result);
});
}
And my test code invokes it like this:
public void Test()
{
var httpClient = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true });
CallClient(httpClient, "User1", "Password1");
CallClient(httpClient, "User2", "Password2");
}
As described above, I get the following written to the console:
User1
User1
What I want is:
User1
User2
TL;DR: NET Core is doing a lot to fight you on this approach under the hood.
Not entirely an answer on what to do, but hopefully helpful background on the HttpClientFactory approach, based on my understanding of the components.
First, from the ASP NET Core docs in regards to impersonation:
ASP.NET Core doesn't implement impersonation. Apps run with the app's
identity for all requests, using app pool or process identity. If the
app should perform an action on behalf of a user, use
WindowsIdentity.RunImpersonated in a terminal inline middleware in
Startup.Configure. Run a single action in this context and then close
the context.
RunImpersonated doesn't support asynchronous operations and shouldn't
be used for complex scenarios. For example, wrapping entire requests
or middleware chains isn't supported or recommended.
As you call out, there's a lot of progress NET Core has made around how HttpClient instances are handled to resolve socket exhaustion and the expensive operations around the underlying handlers. First, there's HttpClientFactory, which in addition to supporting creating named/typed clients with their own pipelines, also attempts to manage and reuse a pool of primary handlers. Second, there's SocketsHttpHandler, which itself manages a connection pool and replaces the previous unmanaged handler by default and is actually used under the hood when you create a new HttpClientHandler. There's a really good post about this on Steve Gordon's Blog: HttpClient Connection Pooling in NET Core. As you're injecting instances of HttpClient around from the factory, it becomes way safer to treat them as scoped and dispose of them because the handlers are no longer your problem.
Unfortunately, all that pooling and async-friendly reuse makes your particular impersonation case difficult, because you actually need the opposite: synchronous calls that clean up after themselves and don't leave the connection open with the previous credentials. Additionally, what used to be a lower-level capability, HttpWebRequest now actually sits on top of HttpClient instead of the other way around, so you can't even skip it all that well by trying to run the requests as a one off. It might be a better option to look into using OpenID Connect and IdentityServer or something to centralize that identity management and Windows auth and pass around JWT everywhere instead.
If you really need to just "make it work", you might try at least adding some protections around the handler and its connection pooling when it comes to the instance that is getting used to make these requests; event if the new clients per request are working most of the time, deliberately cleaning up after them might be safer. Full disclaimer, I have not tested the below code, so consider it conceptual at best.
(Updated Switched the static/semaphore to a regular instance since the last attempt didn't work)
using (var handler = new SocketsHttpHandler() { Credentials = CredentialCache.DefaultCredentials, PooledConnectionLifetime = TimeSpan.Zero, MaxConnectionsPerServer = 1 })
using (var client = new HttpClient(handler, true))
{
return client.GetStringAsync(uri).Result;
}
Someone have idea how to implement authentication and autorization process in vert.x microservice architecture thru API Gateway service and standalone user service, where it has own custom Auth provider? Should it use shared data (hazelcast map) to store some user information like name, roles by token or something else? I,m breaking mind..
As part of a microservices architecture, I'd not implement the authentication directly in my vert.x microservice but I'd go through with an API management gateway like tyk or kong for example.
Then I'd only check the permissions of the user over the data in my microservice using any database you want.
You can also dig this track if you really want to handle authentication directly in your microservice.
Having a user service, I wanted all authentication and authorization operations to be performed through it.
In the end, I did this:
jdbcAuth.rxAuthenticate(authInfo)
.map(user -> user.principal().getString("username"))
.flatMap(username -> roleRepository.findByUsername(username).map(roles -> {
JWTOptions jwtOptions = new JWTOptions().setAlgorithm("RS256");
roles.forEach(jwtOptions::addPermission);
LOGGER.info("Token successfully created");
String token = jwtAuth.generateToken(new JsonObject().put("username", username), jwtOptions);
return new JsonObject().put("accessToken", token).put("tokenType", "Bearer");
}))
.subscribe(SingleHelper.toObserver(handler));
For user authentication I use JDBCAuth (and solt hash generation for new user creation)
If there is such user we are getting roles, generate a token with optional claims like "username" and JWTOptions with perrmissions.
Putting into request header Authorization: Bearer --token--
On other microservices we put global JWTAuth handler with pulicKey like:
JWTAuthHandler.create(JWTAuth.create(vertx.getDelegate(), new JWTAuthOptions().addPubSecKey(new PubSecKeyOptions()
.setAlgorithm("RS256")
.setPublicKey(<PUBLIC_KEY>))));
This handler sets the user to the context, and we can get it through context.user () and check the permissions. By the way, we can put this handler along with the addAuthority() method on the route for access checking.
I am using Identity Server 4 to secure an mvc app and all peripheral APIs. During principal creation I'm using the claims transformation middleware, in which I'd like to make a call to a secured API for additional information.
The claims transformation middleware is wired up in the Configure method like so:
app.UseClaimsTransformation(o => new ClaimsTransformer().TransformAsync(o));
And the ClaimsTransformer class looks like:
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
// meat and potatoes goes here...
return context.Principal;
}
I've tried getting the access token from the ClaimsTransformationContext with the following code within TransformAsync, however this seems to result in an infinite loop back to the TransformAsync method on the ClaimsTransformer class.
var accessToken = context.Context.Authentication.GetTokenAsync("access_token");
Is it possible to retrieve the access token during the .net core claims transformation?
Or is this specific API call something htat shouldn't be done on behalf of the user and better handled one step up and suited for a more server to server approach (client credentials grant)?
I am already using a custom authentication provider in my ServiceStack based web services application.
I'm overriding the Authenticate method, and validating my user against one of multiple backend tenant databases. I currently determine the tenant database by matching an API key to a database string.
public override object Authenticate(
IServiceBase authService,
IAuthSession session,
Auth request) // <- custom object here, MyCustomAuth request
{
// ...
}
This works when each application is for a single tenant (a tenant/customer can build their own application and use that API key). Moving forward I want to build a multi-tenant mobile application. Thus the API key method cannot be used because I can't expect each user to type it in, hence I can't determine which tenant is using the application.
I wanted to alter the Auth object so that I could include the TenantId (provided by the user on login). However, I can't see how I can customize that object.
Is there anyway to customize that Auth object, or do I have to find an alternative solution?
You can't modify the built-in Authenticate Request DTO used, but you can use its Dictionary<string, string> Meta property to send additional metadata with the Authenticate request, e.g:
client.Post(new Authenticate {
...
Meta = new Dictionary<string,string> {
{"TenantId", tenantId},
}
}
Alternatively you can send additional info in the QueryString or HTTP Headers and access the IRequest with:
var tenantId = authService.Request.QueryString["TenantId"];
I am building an app where I use Scribe for all my oauth needs. I create a service API class overriding DefaultApi20 with my end points for authorization and token URLs.
However for Shopify, the authorization URL is dependent on another parameter (Eg: shop name) where the authorization url needs shopname as subdomain. How do I send parameters for this?
I can do the oauth manually constructing the auth url and token but I am looking for a better way to construct sending custom parameters.
Thanks.
We had a similar situation where a variable on the API had to set differently for different users. We did the following:
-Created a custom serviceImpl which extended OAuth10aServiceImpl (may be OAuth20ServiceImpl in your case).
-gave it a method to set the variable on it's api class
-after service is created by your ServiceBuilder lookup the appropriate value and call the setter method of the service.
-continue with normal OAUth token flow
Note that you also need to let the API know to use the custom service class, for example:
#Override
OAuthService createService(OAuthConfig config)
{
return new CustomServiceImpl(this,config)
}
Hope that helps