locked
I'm stuck: Can't get user name for creating audit tracking information, when published on IIS RRS feed

  • Question

  • User-1319828455 posted

    Hello everyone,

    this is my first asp.net core project - in fact it's one of my first web projects and I'm using blazor as ui technology.
    One requirement is having an audit trail to track changes by all users, so I'd also need the username for every audit entry.

    This is working pretty fine when running the application from my debugger, however, when I publish the application on IIS, I only get "MachineName\ApplicationPoolIdentity" when checking "User.Identity" in the controller. On IIS I got "Windows Authentication" enabled, "Anonymous Authentication" disabled.

    Now I already read, that on IIS the application pool identity is passed on as user identity. When I want to pass the identity of the user, which is logged in on the web application, I'd have to wrap all http requests in WindowsIdentity.RunImpersonated(). This, however, doesn't also seem to be recommended.

    My question is, how can I get the UserName of the logged in user in my controller or service classes, in order to write my audit entries?

    Best regards

    Pete

    Thursday, September 17, 2020 12:09 PM

All replies

  • User753101303 posted

    Hi,

    "This, however, doesn't also seem to be recommended." Would say so as well. To me this is a "workaround" ie you add more code so that you get the expected behavior when instead you should be able to fix the source issue.

    I wopuld start by looking at User.Identity.IsAuthenticated. You are sure you are using User.Identity.Name. A common problem is to use instead a method that returns the name of the account under which the code runs which doesn't always match the name of the authenticated user.

    Edit: you followed https://docs.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.1&tabs=visual-studio ?

    Thursday, September 17, 2020 1:08 PM
  • User-1319828455 posted

    Hi PatriceSc,

    first of all, thanks for your reply!

    "User.Identity.IsAuthenticated" returns "true" and "User.Identity.Name" returns "IIS APPPOOL\AppPoolName" and not the "username".

    Take a look at my controller method:

        [Route("/api/[controller]")]
        [ApiController]
        public class TestController : ControllerBase
        {
            [HttpGet]
            public async Task<ActionResult<string>> GetTest()
            {
                var isAuth = User.Identity.IsAuthenticated == true ? "yes" : "no";
                var test1 = User.Identity.Name;
                var test2 = this.HttpContext.User.Identity.Name;
    
                return Ok($"{test1} / {test2} - {isAuth}");
            }
    }

    Both "User.Identity.Name" and "HttpContext.User.Identity.Name" return "IIS APPPOOL\AppPoolName".

    this is my ConfigureServices Method:

     public void ConfigureServices(IServiceCollection services)
            {
                services.AddAuthentication(IISDefaults.AuthenticationScheme);
    
                services.Configure<IISServerOptions>(options =>
                {
                    options.AutomaticAuthentication = true;
                });
    
                services.AddRazorPages();
                services.AddServerSideBlazor();
    
                services.AddHttpClient("testclient", c =>
                    {
                        c.BaseAddress = new Uri("http://x.x.x.x:x/");
                    })
                    .ConfigureHttpMessageHandlerBuilder(x => x.PrimaryHandler = new HttpClientHandler()
                    {
                        UseDefaultCredentials = true,
                    });
    
                services.AddMvc().AddNewtonsoftJson(o =>
                {
                    o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                    o.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
                });
            }

    I read all the stuff on the link you provided, from top to bottom.

    What I dont get is, when I call "@context.User.Identity.Name" from a Razor page, I get the correct user name, but in a controller, after I do a httprequest, I only get "IIS APPPOOL\AppPoolName". Why is that?

    Edit: I should mention, that when wrapping the httprequest in "await WindowsIdentity.RunImpersonated()" and passing the current windowsIdentity accesstoken, I get the correct user. However, I'm not sure if this is best practice for my scenario.

    Kind regards
    Peter

    Friday, September 18, 2020 12:50 PM
  • User-474980206 posted

    your question is a little obscure. If the razor page is getting the correct name, then you configured everything correctly. I'm guessing, that your controller is calling a service (database / webapi) with authentication and its getting the controller thread identity. 

    if this is the case, and the service requires windows authentication, then token impersonation is required. but due to asp.net core async design, you will need to create a new thread to do the impersonation and calls with.  

    note: bearer tokens are the modern approach to passing credentials between services, so I would not expect much improvement for windows authentication. You can configure a oauth server to use windows authentication to get silent login. this may be a better long term approach.

    Friday, September 18, 2020 3:24 PM
  • User753101303 posted

    Could it be that the user logs to a web site that in turns call this controller through http?

    Then it is expected. The user is authenticated to the first site but the code runs using the application pool account. So if this code calls another service, this is the application pool account that is used rather than the original user account. This is for safety reasons (else a malicious developer could run whatever he wants as any user using his site). With ASP.NET 4.x you could enable that using https://techcommunity.microsoft.com/t5/iis-support-blog/setting-up-kerberos-authentication-for-a-website-in-iis/ba-p/324644 (ir you have to allow the web server to forward the user identity to the needed services).

    I tried that once to use the user identity to connect to a database as the user of the web site. Never trrid yet with ASP.NET Core.

    Or impersonation does work fine for you (or this is a test and you hardcoded a particular Windows account ?). 

    Friday, September 18, 2020 5:18 PM
  • User585649674 posted

    we dont face thisproblem. We have aspnet core installed in IIS with windows authentication. We get the windows user name in controller. Please post your program.cs, startup.cs and web.config.

    IIS express runs on user account, so while debugging you will see your name.

    App pool identity should be used only for accessing resources such DB connection, settings file etc.

    But the request context should use the users identity.

    Sunday, September 20, 2020 3:18 PM
  • User-1319828455 posted

    hi nideeshm,

    this is my startup.cs:

        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            public void ConfigureServices(IServiceCollection services)
            {
                services.Configure<IISOptions>(options =>
                {
                    options.ForwardClientCertificate = false;
                    options.AutomaticAuthentication = true;
                });
                services.AddAuthentication(IISDefaults.AuthenticationScheme);
                services.AddRazorPages();
                services.AddServerSideBlazor();
    
    
                services.AddHttpClient("testclient", c =>
                    {
                        c.BaseAddress = new Uri("http://x.x.x.x:9192/");
                    })
                    .ConfigureHttpMessageHandlerBuilder(x => x.PrimaryHandler = new HttpClientHandler()
                    {
                        UseDefaultCredentials = true,
                    });
    
                services.AddMvc().AddNewtonsoftJson(o =>
                {
                    o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                    o.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
                });
    
    
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                app.UseAuthentication();
    
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Error");
                    app.UseHsts();
                }
    
                app.UseHttpsRedirection();
                app.UseStaticFiles();
    
                app.UseRouting();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapDefaultControllerRoute();
                    endpoints.MapBlazorHub();
                    endpoints.MapFallbackToPage("/_Host");
                });
            }
        }

    my program.cs:

        public class Program
        {
            public static void Main(string[] args)
            {
                CreateHostBuilder(args).Build().Run();
            }
    
            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
                            .UseStartup<Startup>();
                    });
        }

    and finally, the contents of my web.config:

    <configuration>
      <location path="." inheritInChildApplications="false">
        <system.webServer>
          <security>
            <authentication>
              <anonymousAuthentication enabled="false" />
              <windowsAuthentication enabled="true" useKernelMode="true">
                            <providers>
                                <clear />
                                <add value="NTLM" />
                                <add value="Negotiate" />
                            </providers>
                            <extendedProtection tokenChecking="None" />
                        </windowsAuthentication>
            </authentication>
          </security>
          <handlers>
            <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
          </handlers>
          <aspNetCore processPath=".\BlazorTestWindowsAuthIIS.exe" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
        </system.webServer>
      </location>
    </configuration>

    The IIS app pool currently runs under AppPoolIdentity. I also tried to set a service account as app pool identity, which made no difference.

    Kind regards

    Peter

    Monday, September 21, 2020 9:13 AM
  • User753101303 posted

    Once again I would expect this behavior if this controller is called from another web app. Before I try can you confirm this controller is called directly from the browser rather than from another (or the same) web app?

    Monday, September 21, 2020 9:44 AM
  • User-1319828455 posted

    @PatriceSc

    It's basically as you assumed. In my testproject I got one asp.net core web project, which containts all (blazor) ui components and also the controllers, which I consume using http requests.

    Impersonation works, but I'm trying to find out if there is an other, more elegant, way.

    Kind regards

    Peter

    Monday, September 21, 2020 9:52 AM