It took me a while to collate my notes for this; hopefully this will be the first in a vaguely related series on using Policy Server.
Policy Server is a product from a company called Solliance, allowing you to control authorisation within your application (as opposed to authentication - the same company produces a sister product, called Identity Server). The idea is that, the user is authenticated to determine they are who they say they are, but then authorised to use certain parts of the application, or to do certain things.
This post discusses setting up the open source version of Policy Server. There is a commercial version of this.
The ReadMe on the above GitHub page is quite extensive, however, when I was trying this out, I had some areas that I thought weren’t too clear. This post will explain that to me in the future, should I become unclear again (a common occurrence when you get to a certain age!)
It’s worth bearing in mind that authorisation and authentication need to be done on the server; whilst you may choose to also do that on the client, the code on the client is, well, on the client! If you have some Javascript code sat in the web browser that’s stopping me from seeing the defence plans, then that’s just not secure enough.
Installing and Configuring Policy Server
The first step here is to install the NuGet package:
Install-Package PolicyServer.Local
Next, add Policy Server in startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version\_2\_2);
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
services.AddPolicyServerClient(Configuration.GetSection("Policy"));
}
Amazingly, at this stage, you’re basically done. The next stage is to add the rules into the appsettings.json. There is an example on the page above, but here’s a much simpler one:
"Policy": {
"roles": [
{
"name": "salesmanager",
"subjects": [ "1" ]
},
{
"name": "driver",
"subjects": [ "2" ]
}
],
"permissions": [
{
"name": "ViewRoutes",
"roles": [ "driver" ]
},
{
"name": "CreateSalesOrder",
"roles": [ "salesmanager" ]
},
{
"name": "Timesheet",
"roles": [ "salesmanager", "driver" ]
}
]
}
This is, in fact, the part that I didn’t feel was clear enough in the GitHub readme. What, exactly, are “subjects”, and how do I associate them to anything useful? I’ll come back to this shortly, but first, let’s see the rules that we have set up here:
The Sales Manager can create a sales order, and complete their timesheet.
The Driver can view routes and complete their timesheet.
The Sales Manager can not view routes; nor can the driver create a sales order.
Add Policy Server to a Controller
The next stage is to inject Policy Server into our controller. For the home controller, all you need to do is inject IPolicyServerRuntimeClient:
private readonly IPolicyServerRuntimeClient \_policyServerRuntimeClient;
public HomeController(IPolicyServerRuntimeClient policyServerRuntimeClient)
{
\_policyServerRuntimeClient = policyServerRuntimeClient;
}
This can now be used anywhere in the controller. However, for testing purposes, let’s just circle back round to the subject question. The subject is the identity of the user. You may have noticed that I’ve started this post with PolicyServer, so I don’t have any authentication in place here. However, for testing purposes, we can force our user to a specific identity.
Let’s override the Index method of the HomeController:
public async Task<IActionResult> Index()
{
var identity = new ClaimsIdentity(new Claim[]
{
new Claim("sub", "2"),
new Claim("name", "TestUser")
}, "test");
User.AddIdentity(identity);
var homeViewModel = await SecureViewModel();
return View(homeViewModel);
}
We’ll come back to the ViewModel business shortly, but let’s focus on the new ClaimsIdentity; notice that the “sub” (or subject) refers to the Subject ID of the driver above. So what we’ve done is created a driver!
The SecureViewModel method returns a ViewModel (which, as you can see, is passed to the view); let’s see what that might look like:
public async Task<HomeViewModel> SecureViewModel()
{
var homeViewModel = new HomeViewModel();
homeViewModel.ViewRoutes = await \_policyServerRuntimeClient.HasPermissionAsync(User, "ViewRoutes");
homeViewModel.CreateSalesOrder = await \_policyServerRuntimeClient.HasPermissionAsync(User, "CreateSalesOrder");
homeViewModel.Timesheet = await \_policyServerRuntimeClient.HasPermissionAsync(User, "Timesheet");
return homeViewModel;
}
You can play about with how this works by altering the “sub” claim.
How does this fit into Identity Server (or an identity server such as Azure B2C)?
The identity result of the authentication, should map to the “sub” claim. In some cases, it’s “nameidentitifier”. Once you’ve captured that, you’ll need to store that reference against the user record.
That’s all very well for testing, but when I use this for real, I want to plug my data in from a database
The first thing you’ll need to do is to ‘flatten’ the data. this StackOverflow question was about the best resource I could find for this, and it gets you about 90% of the way there. I hope to come back to that, and linking this in with Asp.Net Core Policies in the next post.
References
https://auth0.com/blog/role-based-access-control-rbac-and-react-apps/
https://github.com/IdentityServer/IdentityServer4/issues/2968
https://stackoverflow.com/questions/55450393/persist-entitys-in-database-instead-of-appsettings-json
https://stackoverflow.com/questions/55450393/persist-entitys-in-database-instead-of-appsettings-json