Search code examples
asp.net-coreidentityserver4asp.net-core-identity

IdentityServer4 error when using WindowsAuthentication


I successfully followed the Deblokt tutorial on IdentityServer4 (https://deblokt.com/2020/01/24/01-identityserver4-quickstart-net-core-3-1/). This tutorial uses the IdentityServer4 QuickstartUI. Userid/password authentication using the AspNet Identity database is working.

I'm using the latest .Net Core (3.1), which differs slightly from the tutorial, but it is working.

Now I'm attempting to add Windows Authentication and hitting a runtime error. What befuddles me is that the error is occurring after the user is successfully authenticated by windows. I'm running locally on a Windows 10 machine inside Visual Studio 2019 in debug mode, using Google Chrome.

Per the IdentityServer4 documentation (https://identityserver4.readthedocs.io/en/latest/topics/windows.html#using-kestrel) I added this to my Startup.cs:

services.Configure<IISServerOptions>(iis =>
{
    iis.AuthenticationDisplayName = "Windows";
    iis.AutomaticAuthentication = false;
});

I then right-clicked the project, chose "Properties", chose the "Debug" tab, and checked "Windows Authentication":

enter image description here

This makes the "Windows" button visible when I navigate to /Account/Login:

enter image description here

When the "Windows" button is clicked the postback is handled inside the ExternalController "Challenge" method, with "Windows" as the provider argument and "~/" for the returnUrl argument. Since the provider argument is "Windows" this method calls ProcessWindowsLoginAsync, passing the returnUrl argument.

The first time, ProcessWindowsLoginAsync calls HttpContext.AuthenticateAsync("Windows"), which returns a null Principal, which causes the method to re-call /External/Challenge("Windows"), which calls ProcessWindowsLoginAsync a second time, which in turn calls HttpContext.AuthenticateAsync("Windows"), and this 2nd call is successful--HttpContext.AuthenticateAsync returns a windows principal (which is me) and I can view it in the debugger and see that it has all of my particulars correct.

ProcessWindowsLoginAsync then makes this call:

await HttpContext.SignInAsync(
    IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
    new ClaimsPrincipal(id),
    props
);

This executes without error. The "id" argument is a ClaimsIdentity with 2 claims pulled from my windows identity, Subject and Name.

At this point all appears to be well. The method ProcessWindowsLoginAsync ends with a Redirect to /External/Callback. This is where the error occurs. The method /External/Callback method contains this bit of code at the top, and is throwing the exception you see. For reasons unknown HttpContext.AuthenticateAsync is not succeeding.

var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
if (result?.Succeeded != true)
{
    throw new Exception("External authentication error");
}

The "result" object has a "Succeeded" value of false and a "None" value of true, the other properties (Principal, Properties, and Ticket) are all null.

Any advice is appreciated.


Solution

  • The fix is to change this, on ExternalController.cs line 173:

    await HttpContext.SignInAsync(
               IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
                new ClaimsPrincipal(id),
                props);
    

    To this:

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

    This seems obvious in hindsight: AuthenticateAsync and SignInAsync should reference the same scheme.

    After bumbling across the solution I notice that the IdentityServer4 github already has a closed bug report from March 2018, I don't know why this bug is still part of the Quickstart though: https://github.com/IdentityServer/IdentityServer4/issues/2166