ASP.NET Core Authorization for Windows, Local accounts

This article shows how authorization could be implemented for an ASP.NET Core MVC application. The authorization logic is extracted into a separate project, which is required by some certification software requirements. This could also be deployed as a separate service.

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

Blogs in this series:

History

2020-08-23 Updated to .NET Core 3.1, IdentityServer4 V4

2019-09-12 Updated to .NET Core 3.0

Application Authorization Service

The authorization service uses the claims returned for the identity of the MVC application. The claims are returned from the ASP.NET Core MVC client app which authenticates using the OpenID Connect Hybrid flow. The values are then used to create or define the authorization logic.

The authorization service supports a single API method, IsAdmin. This method checks if the username is a defined admin, and that the person/client used a Windows account to login.

using System;

namespace AppAuthorizationService
{
    public class AppAuthorizationService : IAppAuthorizationService
    {
        public bool IsAdmin(string username, string providerClaimValue)
        {
            return RulesAdmin.IsAdmin(username, providerClaimValue);
        }
    }
}

The rules define the authorization process. This is just a simple static configuration class, but any database, configuration files, authorization API could be used to check, define the rules.

In this example, the administrators are defined in the class, and the Windows value is checked for the claim parameter.

using System;
using System.Collections.Generic;
using System.Text;

namespace AppAuthorizationService
{
    public static class RulesAdmin
    {

        private static List<string> adminUsers = new List<string>();

        private static List<string> adminProviders = new List<string>();

        public static bool IsAdmin(string username, string providerClaimValue)
        {
            if(adminUsers.Count == 0)
            {
                AddAllowedUsers();
                AddAllowedProviders();
            }

            if (adminUsers.Contains(username) && adminProviders.Contains(providerClaimValue))
            {
                return true;
            }

            return false;
        }

        private static void AddAllowedUsers()
        {
            adminUsers.Add("SWISSANGULAR\\Damien");
        }

        private static void AddAllowedProviders()
        {
            adminProviders.Add("Windows");
        }
    }
}

ASP.NET Core Policies

The application authorization service also defines the ASP.NET Core policies which can be used by the client application. An IAuthorizationRequirement is implemented.

using Microsoft.AspNetCore.Authorization;
 
namespace AppAuthorizationService
{
    public class IsAdminRequirement : IAuthorizationRequirement{}
}

The IAuthorizationRequirement implementation is then used in the AuthorizationHandler implementation IsAdminHandler. This handler checks, validates the claims, using the IAppAuthorizationService service.

using Microsoft.AspNetCore.Authorization;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace AppAuthorizationService
{
    public class IsAdminHandler : AuthorizationHandler<IsAdminRequirement>
    {
        private IAppAuthorizationService _appAuthorizationService;

        public IsAdminHandler(IAppAuthorizationService appAuthorizationService)
        {
            _appAuthorizationService = appAuthorizationService;
        }

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsAdminRequirement requirement)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            if (requirement == null)
                throw new ArgumentNullException(nameof(requirement));

            var claimIdentityprovider = context.User.Claims.FirstOrDefault(t => t.Type == "http://schemas.microsoft.com/identity/claims/identityprovider");

            if (claimIdentityprovider != null && _appAuthorizationService.IsAdmin(context.User.Identity.Name, claimIdentityprovider.Value))
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

As an example, a second policy is also defined, which checks that the http://schemas.microsoft.com/identity/claims/identityprovider claim has a Windows value.

using Microsoft.AspNetCore.Authorization;

namespace AppAuthorizationService
{
    public static class MyPolicies
    {
        private static AuthorizationPolicy requireWindowsProviderPolicy;

        public static AuthorizationPolicy GetRequireWindowsProviderPolicy()
        {
            if (requireWindowsProviderPolicy != null) return requireWindowsProviderPolicy;

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

            return requireWindowsProviderPolicy;
        }
    }
}

Using the Authorization Service and Policies

The Authorization can then be used, by adding the services to the Startup of the client application.

services.AddSingleton<IAppAuthorizationService, AppAuthorizationService.AppAuthorizationService>();
services.AddSingleton<IAuthorizationHandler, IsAdminHandler>();

services.AddAuthorization(options =>
{
	options.AddPolicy("RequireWindowsProviderPolicy", MyPolicies.GetRequireWindowsProviderPolicy());
	options.AddPolicy("IsAdminRequirementPolicy", policyIsAdminRequirement =>
	{
		policyIsAdminRequirement.Requirements.Add(new IsAdminRequirement());
	});
});

The policies can then be used in a controller and validate that the IsAdminRequirementPolicy is fulfilled.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace MvcHybridClient.Controllers
{
    [Authorize(Policy = "IsAdminRequirementPolicy")]
    public class AdminController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

Or the IAppAuthorizationService can be used directly if you wish to mix authorization within a controller.

private IAppAuthorizationService _appAuthorizationService;

public HomeController(IAppAuthorizationService appAuthorizationService)
{
	_appAuthorizationService = appAuthorizationService;
}

public IActionResult Index()
{
	// Windows or local => claim http://schemas.microsoft.com/identity/claims/identityprovider
	var claimIdentityprovider = 
	  User.Claims.FirstOrDefault(t => 
	    t.Type == "http://schemas.microsoft.com/identity/claims/identityprovider");

	if (claimIdentityprovider != null && 
	  _appAuthorizationService.IsAdmin(
	     User.Identity.Name, 
		 claimIdentityprovider.Value)
	)
	{
		// yes, this is an admin
		Console.WriteLine("This is an admin, we can do some specific admin logic!");
	}

	return View();
}

If an admin user from Windows logged in, the admin view can be accessed.

Or the local guest user only sees the home view.


Notes:

This is a good way of separating the authorization logic from the business application in your software. Some certified software processes, require that the application authorization, authentication is audited before each release, for each new deployment if anything changed.
By separating the logic, you can deploy, update the business application without doing a security audit. The authorization process could also be deployed to a separate process if required.

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/

4 comments

  1. […] ASP.NET Core Authorization for Windows, Local accounts – Damien Bowden […]

  2. […] ASP.NET Core authorization for Windows, Local accounts […]

  3. Not sure what

    http://schemas.microsoft.com/identity/claims/identityprovider

    is – IS4 does not emit such a claim… 😉

  4. Imran Khan · · Reply

    I am unable to run this in IIS. It fails when I hit “Windows”. This is where it is giving error in “ExternalLoginCallback()” method. This works fine in IISExpress but not in IIS.

    var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);

    result.Succeeded always comes as false.

Leave a comment

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