How to implement Windows Authentication with IdentityServer 4

There will be more documentation soon here:

https://identityserver4.readthedocs.io

But in short - yes from IdentityServer's point of view Windows authentication is an external provider (as opposed to the IS native authentication cookie).

There is nothing that YOU need to do to implement Windows authentication - just use a host that supports it.

That's either

  • Kestrel with IIS integration
  • WebListener

In both cases you invoke the Windows machinery by challenging a scheme of either Negotiate or NTLM. This is not IS specific - but the way ASP.NET Core works.

Our quick start UI shows how to do that - check the AccountController.

https://github.com/IdentityServer/IdentityServer4.Quickstart.UI


For anyone coming across this in search results that is having trouble meshing the quickstart with the ASPNET Identity quickstart, here are the missing pieces.

For the most part you want to use the ASPNET Identity code, utilizing the SignInManager to do the heavy lifting. Once you get there and add the Window auth code from the quick start, you should get to the point where everything looks like it is working, but you get null at this line in the callback:

ExternalLoginInfo info = await _signInManager.GetExternalLoginInfoAsync();

To get Windows treated as a real External provider, instead of adding "scheme" to the auth properties around line 163, you want to change the key to "LoginProvider":

properties.Items.Add("LoginProvider", AccountOptions.WindowsAuthenticationSchemeName);

I use a domain query to pull extra info on my users, looks something like this:

using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain))
using (UserPrincipal up = UserPrincipal.FindByIdentity(pc, wp.Identity.Name))
{
    if (up == null)
    {
        throw new NullReferenceException($"Unable to find user: {wp.Identity.Name}");
    }

    id.AddClaim(new Claim(ClaimTypes.NameIdentifier, up.Sid.Value));
    id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
    id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
    id.AddClaim(new Claim(JwtClaimTypes.Email, up.EmailAddress));
    id.AddClaim(new Claim(Constants.ClaimTypes.Upn, up.UserPrincipalName));
    id.AddClaim(new Claim(JwtClaimTypes.GivenName, up.GivenName));
    id.AddClaim(new Claim(JwtClaimTypes.FamilyName, up.Surname));
}

What claims you add is up to you, but you NEED one of type ClaimTypes.NameIdentifier for the SigninManager to find. SID seems like the best use to me. Last thing to change is the SignInAsync call to use the correct scheme around line 178-181:

await HttpContext.SignInAsync(IdentityConstants.ExternalScheme, new ClaimsPrincipal(id), properties);

Unless you are overriding the default Schemes that IdentityServer4 is using in .net core 2, this is the correct default scheme. And now your call to GetExternalLoginInfoAsync in the callback will work and you can continue on!