runtime: WindowsIdentity.RunImpersonated sending wrong credentials

Hi all!

I’m experiencing the problem described in issue https://github.com/dotnet/runtime/issues/38414.

I have a ASP.NET Core app with a Middleware, that authenticate user via windows authentication and runs under his account an http-request to get a jwt-token.

Startup.cs

 public class Startup
    {
      //...
      //omitted for brevity
      //...
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDistributedMemoryCache();
            services.AddSession(conf => conf.Cookie.MaxAge = TimeSpan.FromMinutes(30));
            services.AddAuthentication(IISDefaults.AuthenticationScheme).AddIdentityServerAuthentication(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                options.Authority = _configuration.GetValue<string>("IdentityServer");
                options.SupportedTokens = SupportedTokens.Both;
                options.ApiSecret = _configuration.GetValue<string>("ClientSecret");
                options.RequireHttpsMetadata = false;
            });
            services.AddHttpClient(ServiceConsts.ImpersonatedHttpClientName)
                .ConfigurePrimaryHttpMessageHandler(_ =>
                    new SocketsHttpHandler()
                    {
                        UseProxy = false,
                        Credentials = CredentialCache.DefaultCredentials
                    });

            services.Configure<ForwardedHeadersOptions>(options =>
            {
                options.ForwardedHeaders =
                    ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;

                options.KnownNetworks.Clear();
                options.KnownProxies.Clear();
            });
          //...
          //omitted for brevity
          //...
        }
      
       public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseSession();
            
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseForwardedHeaders();
            app.Use(async (context, next) =>
            {
                string ip = context.Connection.RemoteIpAddress.ToString();
                context.Request.Headers[ForwardedIpToken] = ip;
                await next.Invoke();
            });
            app.UseMiddleware<WinTokenMiddleware>();
            app.UseOcelot().Wait();
        }

      //...
      //omitted for brevity
      //...
}

WinTokenMiddleware.cs

   [SupportedOSPlatform("windows")]
    public class WinTokenMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IConfiguration _configuration;
        private readonly IHttpClientFactory _httpClientFactory;
        private static readonly ILogger Logger = Log.ForContext<WinTokenMiddleware>();
        private const string TokenSessionKey = "AuthToken";

        public WinTokenMiddleware(RequestDelegate next, IConfiguration configuration, IHttpClientFactory httpClientFactory)
        {
            _next = next;
            _configuration = configuration;
            _httpClientFactory = httpClientFactory;
        }

        // ReSharper disable once UnusedMember.Global
        public async Task InvokeAsync(HttpContext context)
        {
           //...
           //... omitted for brevity
           //...
                AuthenticateResult winAuthenticateResult = await context.AuthenticateAsync(IISServerDefaults.AuthenticationScheme);
                if (!winAuthenticateResult.Succeeded)
                {
                    await context.ChallengeAsync(IISDefaults.AuthenticationScheme);
                    return;
                }

                if (!(winAuthenticateResult?.Principal?.Identity is WindowsIdentity windowsIdentity))
                {
                    Logger.Warning("Couldn't get user windows account!'");
                    await WriteErrorResponse(context,
                        "Couldn't get user windows account! Please enable windows authentication on server.");
                    return;
                }
           //...
           //... omitted for brevity
           //...
                TokenResponse tokenResponse = await WindowsIdentity.RunImpersonated(
                        windowsIdentity.AccessToken,
                        () =>
                        {
                            Logger.Debug($"Current user {WindowsIdentity.GetCurrent().Name}");
                            HttpClient impersonatedClient = _httpClientFactory.CreateClient(ServiceConsts.ImpersonatedHttpClientName);

                            //получаем токен под пользователем
                            return impersonatedClient.RequestTokenAsync(new TokenRequest()
                            {
                                Address = disco.TokenEndpoint,

                                ClientId = _configuration.GetValue<string>("ClientId"),
                                ClientSecret = _configuration.GetValue<string>("ClientSecret"),
                                GrantType = "windows"

                            });
                        });
//...
//... omitted for brevity
//...
        }
}

The Code above gets jwt-Token authenticated under user account with windows authentication. When we deploy application, we see that users get tokens of other users. For example, if user A gets authenticated correctly and received his token, and few seconds later comes user B, he gets authenticated by user A and therefore gets the wrong token.

If I set PooledConnectionLifetime to TimeSpan.Zero, it works as expected

            services.AddHttpClient(ServiceConsts.ImpersonatedHttpClientName)
                .ConfigurePrimaryHttpMessageHandler(_ =>
                    new SocketsHttpHandler()
                    {
                        UseProxy = false,
                        Credentials = CredentialCache.DefaultCredentials,
                        PreAuthenticate = false,
                        PooledConnectionLifetime = TimeSpan.Zero
                    });

The service is running on net5.0 on windows server 2016, IIS 10.

Is this a bug or am I doing it the wrong way?

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 33 (18 by maintainers)

Most upvoted comments

Ok, I’ll do it in one or two days. I’ll try to create a minimal solution to check it.

@karelz, ok, I’ll install it, thank you!