Supporting both Local and Windows Authentication in ASP.NET Core MVC using IdentityServer4

This article shows how to setup an ASP.NET Core MVC application to support both users who can login in with a local login account, solution specific, or use a windows authentication login. The identity created from the windows authentication could then be allowed to do different tasks, for example administration, or a user from the local authentication could be used for guest accounts, etc. To do this, IdentityServer4 is used to handle the authentication. The ASP.NET Core MVC application uses the OpenID Connect Hybrid Flow.

Code: https://github.com/damienbod/AspNetCoreWindowsAuth

Posts in this series:

History

2020-08-23 Updated to .NET Core 3.1, IdentityServer4 V4
2019-09-12 Updated to .NET Core 3.0

Setting up the STS using IdentityServer4

The STS is setup using the IdentityServer4 dotnet templates. Once installed, the is4aspid template was used to create the application from the command line.

The windows authentication is activated in the launchSettings.json. To setup the windows authentication for the deployment, refer to the Microsoft Docs.

{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "https://localhost:44364/",
      "sslPort": 44364
    }
  },

The OpenID Connect Hybrid Flow was then configured for the client application.

new Client
{
	ClientId = "hybridclient",
	ClientName = "MVC Client",

	AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
	ClientSecrets = { new Secret("hybrid_flow_secret".Sha256()) },
	RequirePkce = false,
	RedirectUris = { "https://localhost:44381/signin-oidc" },
	FrontChannelLogoutUri = "https://localhost:44381/signout-oidc",
	PostLogoutRedirectUris = { "https://localhost:44381/signout-callback-oidc" },

	AllowOfflineAccess = true,
	AllowedScopes = { "openid", "profile", "offline_access",  "scope_used_for_hybrid_flow" }
},

ASP.NET Core MVC Hybrid Client

The ASP.NET Core MVC application is configured to authenticate using the STS server, and to save the tokens in a cookie. The AddOpenIdConnect method configures the OIDC Hybrid client, which must match the settings in the IdentityServer4 application.

The TokenValidationParameters MUST be used, to set the NameClaimType property, otherwise the User.Identity.Name property will be null. This value is returned in the ‘name’ claim, which is not the default.

services.AddAuthentication(options =>
{
	options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
	options.SignInScheme = "Cookies";
	options.Authority = stsServer;
	options.RequireHttpsMetadata = true;
	options.ClientId = "hybridclient";
	options.ClientSecret = "hybrid_flow_secret";
	options.ResponseType = "code id_token";
	options.GetClaimsFromUserInfoEndpoint = true;
	options.Scope.Add("scope_used_for_hybrid_flow");
	options.Scope.Add("profile");
	options.Scope.Add("offline_access");
	options.SaveTokens = true;
	// Set the correct name claim type
	options.TokenValidationParameters = new TokenValidationParameters
	{
		NameClaimType = "name"
	};
});

Then all controllers can be secured using the Authorize attribute. The anti forgery cookie should also be used, because the application uses cookies to store the tokens.

[Authorize]
public class HomeController : Controller
{

Displaying the login type in the ASP.NET Core Client

Then application then displays the authentication type in the home view. To do this, a requireWindowsProviderPolicy policy is defined, which requires that the identityprovider claim has the value Windows. The policy is added using the AddAuthorization method options.

var requireWindowsProviderPolicy = new AuthorizationPolicyBuilder()
 .RequireClaim("http://schemas.microsoft.com/identity/claims/identityprovider", "Windows")
 .Build();

services.AddAuthorization(options =>
{
	options.AddPolicy(
	  "RequireWindowsProviderPolicy", 
	  requireWindowsProviderPolicy
	);
});

The policy can then be used in the cshtml view.

@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
@{
    ViewData["Title"] = "Home Page";
}

<br />

@if ((await AuthorizationService.AuthorizeAsync(User, "RequireWindowsProviderPolicy")).Succeeded)
{
    <p>Hi Admin, you logged in with an internal Windows account</p>
}
else
{
    <p>Hi local user</p>

}

Both applications can then be started. The client application is redirected to the STS server and the user can login with either the Windows authentication, or a local account.

The text in the client application is displayed depending on the Identity returned.

Identity created for the Windows Authentication:


Local Identity:

Next Steps

The application now works for Windows authentication, or a local account authentication. The authorization now needs to be set, so that the different types have different claims. The identities returned from the Windows Authentication will have different claims, to the identities returned form the local logon, which will be used for guest accounts.

Links:

https://docs.microsoft.com/en-us/aspnet/core/security/authorization/views?view=aspnetcore-2.1&tabs=aspnetcore2x

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-2.1

https://mva.microsoft.com/en-US/training-courses/introduction-to-identityserver-for-aspnet-core-17945

https://stackoverflow.com/questions/34951713/aspnet5-windows-authentication-get-group-name-from-claims/34955119

https://github.com/IdentityServer/IdentityServer4.Templates

https://docs.microsoft.com/en-us/iis/configuration/system.webserver/security/authentication/windowsauthentication/

14 comments

  1. […] on April 15, 2018by admin submitted by /u/mycall [link] [comments] No comments […]

  2. […] Supporting both Local and Windows Authentication in ASP.NET Core MVC using IdentityServer4 (Damien Bowden) […]

  3. Michael Paine · · Reply

    Should the 44318 port instead be port 44364?

    1. Hi Michael, no only the STS application has the windows auth activated.

      Greetings Damien

  4. Hi,

    Thanks for this article.
    Quick question, where is it creating the windows user in the database? I’d like to create the aspnetuser where the username is domain\username at present it’s a guid.

    Cheers
    Bert

  5. Doug Marquardt · · Reply

    fwiw, switching to HttpSys worked fine after I made one small change:

    DisplayName = x.DisplayName

    was changed to

    DisplayName = x.DisplayName ?? x.Name

    in this code

    var providers = schemes
    .Where(x => x.DisplayName != null ||
    (x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
    )
    .Select(x => new ExternalProvider
    {
    DisplayName = x.DisplayName ?? x.Name,
    AuthenticationScheme = x.Name
    }).ToList();

  6. hi @damienbod

    Can you suggest how the solution is deployed? the startup project is StsServer, but what about the rest? The MvcHybrid client doesn’t work. Some documentation would be nice to have.
    Another question, how IS4 ‘knows’ that the user being authenticated comes from the Active Directory? It would be good to understand the principles, i.e. where from the Identity Server picks up the local users’ identity? Is there a way to check if the user belongs to AD (if the machine he’s using is in the AD domain)? I need to understand the flow.
    Thanks.

  7. Hi @damienbod,

    I was wondering if you had any luck in being able to retrieve windows user profile information? Such as first name, last name.

    1. Hi vblain yes I fixed this in the git repo. Will check if this blog needs an update github repo has the extra claims

  8. This is exactly what I have been looking for. I struggled with updating it to IdentityServer 4.0.2 though.

    1. Steve Klabak · · Reply

      Any luck? Struggling as well and I can’t find any good resources online right now.

      1. I have updated this to 4.1.0, github repo is up-to-date

        Greetings Damien

  9. Ivan-Mark Debono · · Reply

    Any chance to have something similar using OpenIddict?

    1. Sounds like a good idea, will have a look at this

      Greetings Damien

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.