I know that this has been asked many times before, but unfortunately not about ASP.NET Core web apps, just the classic ASP.NET web apps. All the answers i\'ve found on the i
IIS will act as a reverse proxy and will be responsible for setting and transmitting to Kestrel the Windows identity of the user. So first, set up IIS to allow both Windows and Anonymous Authentication:
Then, you need to change your web.config to ask IIS to transmit the Windows identity (in case one is found) to your ASP.NET Core application like that: https://stackoverflow.com/a/42163175/6827240
At this point, if you create a controller action with an "[Authorize]" attribute, HttpContext.User.Identity.Name;
should have the value of the Windows identity used by your client. I replied to something similar here: NTLM authentication on specific route in ASP.NET Core
The good thing is that a standard controller action will still work if your client doesn't pass along Windows identity token, while a protected one (using [Authorize] tag) will fail.
PS: I like to use curl.exe in verbose mode to see what is happening in terms of authorization protocol (Negotiate protocol, NTLM tokens ...)
I have a similar scenario for an ASP.NET Core 2.0 application (use Windows Authentication throughout the app except a single controller) and Daboul's explanation was not enough.
I had to set up a custom middleware as indicated here since anonymous takes precedence.
public class NtlmAndAnonymousSetupMiddleware
{
private readonly RequestDelegate next;
public NtlmAndAnonymousSetupMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.User.Identity.IsAuthenticated || context.Request.Path.ToString().StartsWith("/Anonymous"))
{
await next(context);
return;
}
await context.ChallengeAsync("Windows");
}
}
and its usage in Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMiddleware<NtlmAndAnonymousSetupMiddleware>();
// other code here
}
So, the middleware accept anonymous requests for AnonymousController only and will provide a challenge if Windows Authentication info is not provided.
Since the middleware makes the differece between what is anonymous and requires authentication, this will look just like any ordinary controller:
[Route("Anonymous")]
public class AnonymousController : Controller
{
[HttpGet("Echo")]
public string Echo(string data)
{
return data;
}
}
(all done on a Windows machine)
Chrome + access non-anonymous controller action => works fine (both @User.Identity.Name
and @Context.User.Identity.Name
return the correct user
Chrome + anonymous action => works directly
Firefox (which does not directly transfer NTLM ticket from OS) + non-anonymous => a modal asks for user/pass => if provided correctly, it works fine
Firefox + anonymous action => works directly
I have a solution with Windows authentication disabled on IIS. All you need to do is NTLM authentication. Simply put this recursive code in your controller login action:
var result = await HttpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme);
if (!result.Succeeded) {
await HttpContext.ChallengeAsync(IISDefaults.AuthenticationScheme); //performs NTLM handshake
return StatusCode(Response.StatusCode); // sends 401
}
// windows login has already succeed
// get user name and domain
WindowsIdentity winIdentity = (WindowsIdentity)result.Principal.Identity;
..... and so on
In case anyone wonders, I modified @Alexei's answer to use Attributes rather than request path in Netcore 3.X
First create the class and get the endpoints metadata
public class NtlmAndAnonymousSetupMiddleware
{
private readonly RequestDelegate next;
public NtlmAndAnonymousSetupMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.User.Identity.IsAuthenticated || HasAnonymousAttribute(context))
{
await next(context);
return;
}
await context.ChallengeAsync("Windows");
}
private bool HasAnonymousAttribute(HttpContext context)
{
var endpoint = context.GetEndpoint();
var retVal = (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null);
return retVal;
}
}
Then modify public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<NtlmAndAnonymousSetupMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});