Search code examples
asp.net-core.net-corewindows-authentication

How to use Windows authentication on ASP.NET Core subpath only?


I am trying to configure Windows authentication on a subroute only in my ASP.NET Core MVC app.

My problem is that when I add

services.AddAuthentication().AddNegotiate()

I get an error

The Negotiate Authentication handler cannot be used on a server that directly supports Windows Authentication.

which lead me to adding web.config as the docs explained:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <location path="." inheritInChildApplications="false">
        <system.webServer>
            <security>
                <authentication>
                    <anonymousAuthentication enabled="false" />
                    <windowsAuthentication enabled="true" />
                </authentication>
            </security>
        </system.webServer>
    </location>
</configuration>

and the error goes away. However, now the Windows authentication is popping up on each request.

I tried changing the location path to .testendpoint but that then throws the original error at the base path.

So is it possible and how do I make such only /testendpoint will ask for Windows authentication and the remaining of the application will work with whatever other auth I configured in my ASP.NET Core app?


Solution

  • Another way using endpoint routing:

    We have an application schema for the application that will be used all over the app called eavfw.

    Using a custom endpoint here called login/ntlm with metadata new AuthorizeAttribute(NegotiateDefaults.AuthenticationScheme) its only allowed to be visited by a valid windows authenticated user.

    Here we then create the user in our DB using its AD username.

        endpoints.MapGet("/.auth/login/ntlm", async httpcontext =>
        {
            var loggger = httpcontext.RequestServices.GetRequiredService<ILogger<Startup>>();
            var windowsAuth = await httpcontext.AuthenticateAsync(NegotiateDefaults.AuthenticationScheme);
            
            if (!windowsAuth.Succeeded)
            {
                loggger.LogWarning("Not authenticated: Challening"); 
            }
            if (windowsAuth.Succeeded)
            {
                loggger.LogWarning("Authenticated");
               
                var name = string.Join("\\", windowsAuth.Principal.Claims.FirstOrDefault(c => c.Type.EndsWith("name")).Value.Split("\\").Skip(1));
    
                var context = httpcontext.RequestServices.GetRequiredService<DynamicContext>();
                var users = context.Set<SystemUser>();
                var user = await context.Set<SystemUser>().Where(c => c.PrincipalName == name).FirstOrDefaultAsync();
                if (user == null)
                {
                    user = new SystemUser
                    {
                        PrincipalName = name,
                        Name = name,
                        // Email = email,
                    };
                    await users.AddAsync(user);
                    await context.SaveChangesAsync();
                }
                 
                var principal = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] {                                  
                           new Claim(Claims.Subject,user.Id.ToString())
                        }, "ntlm"))
                {
    
                };
    
                await httpcontext.SignInAsync("ntlm",
                     principal, new AuthenticationProperties(
                                new Dictionary<string, string>
                                {
                                    ["schema"] = "ntlm"
                                }));
    
                httpcontext.Response.Redirect("/account/login/callback");
            }
          
    
        }).WithMetadata(new AuthorizeAttribute(NegotiateDefaults.AuthenticationScheme));
    
    

    using a auxility authentication cookie, we can now make it such that specific areas of our app that requires windows authentication, it can simply rely on Authorize("ntlm") as it automatically forward the authenticate call to check if already signin, and it as part of the signin call in the endpoint above actually sign in eavfw.external before it redirects to the general account callback page that will do some final validation before signing in eavfw from the eavfw.external cookie

                services.AddAuthentication().AddCookie("ntlm", o => {
                    o.LoginPath = "/.auth/login/ntlm";
                    o.ForwardSignIn = "eavfw.external";
                    o.ForwardAuthenticate = "eavfw";
                });
    

    So there are a few ways to extend and use the authentication system in auth core depending on how MVC framework heavy your application is.