'Enable both Windows authentication and Anonymous authentication in an ASP.NET Core app
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 internet don't help me, because the IIS configuration of ASP.NET Core apps is so different than classic ASP.NET. For example, ASP.NET Core uses the Kestrel proxy, so many of the configurations relevant in ASP.NET are not in ASP.NET Core. I've basically tried everything i could possibly found on the Internet but none helped me. I whish it was as simple as enabling both anonymous and windows authentications on the app in IIS and that's it, but I guess it will more work than that.
What is the procedure of enabling both these authentications in a single asp.net core web app?
Solution 1:[1]
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.
The middleware
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.
Anonymous controller
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;
}
}
Tests
(all done on a Windows machine)
Chrome + access non-anonymous controller action => works fine (both
@User.Identity.Nameand@Context.User.Identity.Namereturn the correct userChrome + 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
Solution 2:[2]
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();
});
Solution 3:[3]
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
Solution 4:[4]
When IIS is not under our control - probably a rare case - and we don't know whether windows authentication is even enabled or not the prior solution does not work as an exception is thrown before we reach the endpoint.
The following approach - neither elegant nor performant - might help in this case. The idea is basically to trigger another call to an endpoint to determine if the user is actually a windows loging or not. If this call is successful, then we know we have a windows user and can act accordingly, for example do a redirect to an endpoint that requires windows authentication. However, for most cases, the solution already posted is probably the way to go as this one covers an edge case.
see also: similar entry
Code example (.Net 6):
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> TestWindowsAuthAsync(CancellationToken cancellationToken)
{
using var client = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true
});
var response = await client.GetAsync($"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}{HttpContext.Request.PathBase}{HttpContext.Request.Path}/HasUserWindowsAuth");
if (response.IsSuccessStatusCode)
{
// Yes, now we know that user indeed has windows authentication and we can act upon it
return RedirectToAction("QuickLogin", input);
}
// No windows credentials have been passed at this point
return View();
}
[HttpGet("HasUserWindowsAuth")]
[Authorize(AuthenticationSchemes = IISDefaults.AuthenticationScheme)]
public IActionResult HasUserWindowsAuth() => Ok();
[HttpGet("QuickLogin")]
[Authorize(AuthenticationSchemes = IISDefaults.AuthenticationScheme)]
public async Task<IActionResult> QuickLoginAsync(LoginModel input, CancellationToken cancellationToken)
{
var user = this.User.Identities.FirstOrDefault(i => i System.Security.Principal.WindowsIdentity && i.IsAuthenticatd);
// do something with that user
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Alberto L. Bonfiglio |
| Solution 3 | kavanka |
| Solution 4 | Dan |
