The idea is to use good old fashioned role based authorization with Windows Authentication.
The default behavior of WindowsAuthentication is to load the claims and roles from the windows authorization pipeline (AD, local rights, etc...). This results in the roles or claims to be based on the user groups.
If this behavior does not suit you and you would prefer loading the user roles from a custom storage (database, configuration file, etc...), continue on reading.
To make this happend, you need to implement a class that loads the roles for a user, enable windows authentication and register the role management services.
Note that this implementation is based on simple roles authorization. You can still use the same principle of registering a IClaimsTransformation
that sets up claims that are not used as roles and then define access policies based on these claims.
First enable the windows authentication option in IIS, IIS Express or HTTP.sys (see link below if you don't know how to do it).
Then, if you are using ASP.Net Core 2.x you must register extra services to perform the authentication challenge. To do so, add one of the following line in the ConfigureServices
method.
Using IIS:
// IISDefaults requires the following import:
// using Microsoft.AspNetCore.Server.IISIntegration;
services.AddAuthentication(IISDefaults.AuthenticationScheme);
Using HTTP.sys
// HttpSysDefaults requires the following import:
// using Microsoft.AspNetCore.Server.HttpSys;
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
In addition, Do not forget to add app.UseAuthentication();
to the Configure method of your Startup class.
For detailled instructions, please refer to the documentation page provided by microsoft: Configure Windows Authentication in ASP.NET Core
Include the three files into your projetc (don't forget to fix the namespaces):
- ISimpleRoleProvider.cs
- SimpleRoleAuthorizationTransform.cs
- SimpleRoleAuthorizationServiceCollectionExtensions.cs
To provide the roles for a given user, you must implement a class inheriting from ISimpleRoleProvider
.
The GetUserRolesAsync
method receives the full windows user name including the domain or machine name (eg. MyDomain\Myuser).
Beware that it will be called for each request so keep in mind that it might affect performance.
The example below has hard-coded roles for two users. But feel free to load the roles from any data source like a database or the configuration files. Also note that we defined constants for role names. These are not mandatory but can help avoid typos when restricting access to routes later. As constants, they can be used as parameter to the Routes
property of the AuthorizeAttribute
attribute.
public class DemoSimpleRoleProvider : ISimpleRoleProvider
{
public const string ADMIN = "Admin";
public const string BASIC_USER = "BasicUser";
public Task<ICollection<string>> GetUserRolesAsync(string userName)
{
ICollection<string> result = new string[0];
// Here, John is a basic user and Arnold an admin user
// Feel free to load roles from any source you like.
if (!string.IsNullOrEmpty(userName))
{
if (userName.EndsWith("john", StringComparison.OrdinalIgnoreCase))
result = new[] { BASIC_USER };
else if (userName.EndsWith("arnold", StringComparison.OrdinalIgnoreCase))
result = new[] { BASIC_USER, ADMIN };
}
return Task.FromResult(result);
}
}
The last step to enable simple role authentication is to register the simple role transform and the simple role provider you implemented.
In the ConfigureServices
method, add the following code:
// Setup the custom simple role authorization with
// "DemoSimpleRoleProvider" implementation to provide the roles for a user.
services.AddSimpleRoleAuthorization<DemoSimpleRoleProvider>();
To restrict access to certain roles on your controller and controller routes, you must use the AuthorizeAtribute
and set it's Roles
property to the roles that allowed to access the controller or method.
Here is an example of a controller restricting access. Note that, as we defined constants for role names in our DemoSimpleRoleProvider
, we can use the same constants here:
[Route("api/[controller]")]
[ApiController]
public class DemoController : ControllerBase
{
[HttpGet]
public ActionResult<string> Get()
{
return "Congrats ! Anyone can access this !";
}
[HttpGet("Admin")]
[Authorize(Roles=DemoSimpleRoleProvider.ADMIN)]
public ActionResult<string> GetAdmin()
{
return "Congrats ! You just made an admin call !";
}
[HttpGet("Basic")]
[Authorize(Roles = DemoSimpleRoleProvider.BASIC_USER)]
public ActionResult<string> GetBasic()
{
return "Congrats ! You just made a basic user call !";
}
}
is it working on .net8?