Microsoft Identity Claims: Granular Authorizations, Part 1
Authorizing a user to a page is easy, but what if you have various security points inside a webpage? In part one, we examine a solution to make security even more granular with flags.
Microsoft Identity: Granular Authorizations Series
- Microsoft Identity Claims: Granular Authorizations, Part 1 (this post)
- Microsoft Identity Claims: Isolating Enumerations, Part 2
- Microsoft Identity Claims: Building the UI, Part 3
Microsoft Identity provides multiple levels of security to almost any system. It provides the capabilities to authenticate and authorize users for an application and allows us to extend it even further.
Today's post is a continuation of using claims in a system allowing users to carry out specific tasks in your application.
The Menu System Series was posted back in 2017 and included the following posts:
Overview (or Why Do We Need This?)
Let's say we have a complex screen where we can interact with a number of entities.
For example, we have a web page containing products and the ability to associate a number of images (attachments) to a product. A user can manage all of the products, but only a supervisor can upload attachments to a particular product and publish the product information after review.
With Microsoft Identity, we can attach a user to a role and say they are allowed to view the data, but what about entities on the page where they were allowed access? Microsoft Identity allows access to a page through Policy and Claims. What if you wanted to have a deeper authorization for entities on the page?
Can they add a new product entity? How about a new attachment?
As you can see, we need a way to provide even deeper security to an app instead of allowing page access. Application screens are becoming more complex requiring greater security.
So when you hear about how security should be baked into an application from the very beginning, it's definitely a strong recommendation to examine the system and apply the right measures.
Claims to the Rescue
Using Identity claims makes security extremely easy. You only need to populate the AspNetUserClaims table with the appropriate values for each user.
As I mentioned before, the great part about this is the claims follow around the user identity. You don't need to hit the database to query the permissions, just the Claims collection in the Identity object.
To start, we need to examine the AspNetUserClaims table. In it's current state, we have the following table structure:
Field | Type | Length |
ID | int | N/A |
UserID | varchar | 36 |
ClaimType | varchar | 50 |
ClaimValue | varchar | 100 |
While the ID and UserID is specific to the user, the ClaimType and ClaimValue can be stored as whatever we want...well, so long as it's a string.
If you've implemented the menu system from the previous posts above, the technique in this post will give you another option on how you want to handle security across your app.
Using Flags
One of the great features of C# was the ability to create an enumerated type with flags (one such example was How to Store Enumerated Flags in a Table)
If we're going to use flags, we need to examine everything each entity can do inside an application.
Let's start with the basic maintenance of our entities with CRUD.
[Flags]
public enum PermissionEnum
{
Create = 1,
Retrieve = 2,
Update = 4,
Delete = 8
}
Looking back at our list of example entities, we have products and attachments. A user can create, retrieve, update, and delete a product.
What can you do with an attachment? A user can upload an attachment as well (technically that's creating one, right?). We'll add 'Upload' to our list of permissions in our application.
Let's also setup another sample permission where users can "Send Email."
Now, our PermissionEnum looks like this.
[Flags]
public enum PermissionEnum
{
Create = 1,
Retrieve = 2,
Update = 4,
Delete = 8,
Upload = 16,
SendEmail = 32
}
Of course, your permission flags for your application will vary for each of your entities. The first four flags (CRUD) should be a default for your entities, but your application may have special permissions based on a role or specific function (like sending mail).
BEWARE: I feel I need to make my readers aware of modifying these flags when a system is up-and-running. You can always add onto the existing number of flags, but it's best NOT to remove flags. This will completely mess up your calculations.
Only add, never remove or restructure your flags.
If you absolutely have to change or delete a PermissionEnum, I would recommend updating the AspNetUserClaims table with the appropriate number based on their permissions.
Identify Your Permission Entities
With our PermissionEnum defined, we can focus on identifying the entities requiring permissions. We need a Product and Attachment.
Each of the entities need a key to identify each entity's permission in the AspNetUserClaims table.
I decided to use Enumeration Classes. This allows you to camoflauge your enumeration types as classes. To learn more, check out the enumeration classes on Microsoft Docs website. It's a great way to extend enumerated types.
Here is our example of an enumeration class.
public class Permission : Enumeration
{
public static readonly Permission Product = new Permission(1, "Product");
public static readonly Permission Attachment = new Permission(2, "Attachment");
protected Permission(int id, string groupName) : base(id, groupName) { }
}
Why do we need this?
This allows us to identify a permission attached to an entity's event. It's merely a tag explaining a security point in the application.
Basically, this is the value placed into the ClaimType field in the AspNetUserClaims table.
Creating a Permission Manager
A Permission Manager is what we'll use to determine if a user is authorized to perform a certain task.
When a user logs into your application using Microsoft Identity, it pulls everything related to that user including UserClaim records (as mentioned in a previous post)
This will be our workhorse class which gives authorizations to the user through our PermissionEnum and Permission class.
public class PermissionManager<T> where T : Enum
{
private readonly ApplicationUser _user;
public PermissionManager(ApplicationUser user)
{
_user = user;
}
public bool IsAllowed(Permission element, T permission)
{
var claim = _user.Claims.FirstOrDefault(e => e.ClaimType == element.DisplayName);
if (claim == null)
return false;
var userPermission = claim.ClaimValue.ToInt();
var value = (T) Enum.ToObject(typeof(T), userPermission);
return value.HasFlag(permission);
}
}
UPDATE: Those looking for the ToInt()
extension method, this was referenced here.
Let's walk through this with some UserClaims.
In the AspNetUserClaims, we'll have the following records.
ID | UserID | ClaimType | ClaimValue |
---|---|---|---|
1 | 5 | Product | 15 |
3 | 5 | Attachment | 16 |
What these records tell me is user 5 can Create (1), Retrieve/View (2), Update (4), and Delete (8) Products which gives us a ClaimValue of 15 (1+2+4+8=15).
For Attachments, we allow them to only "Upload" attachments (only Flag 16).
With this syntax, you can create as many permissions as you need by storing the flags in the ClaimValue field.
You can now see how replacing a PermissionEnum could cause some permission issues throughout a system.
If we want to authorize whether a user can create a Product, the following unit test shows how to define and implement it.
[TestMethod]
public void GeneralPermissionTest()
{
// Arrange
var user = new ApplicationUser { Id = "0653DF63-DA57-49E8-B89A-62B737E3D856" };
user.Claims.Add(new ApplicationUserClaim
{
UserId = user.Id,
Id = 1,
ClaimType = "Product",
ClaimValue = "15"
});
var manager = new PermissionManager<PermissionEnum>(user);
// Act
var canCreate = manager.IsAllowed(Permission.Product, PermissionEnum.Create);
var canUpload = manager.IsAllowed(Permission.Product, PermissionEnum.Upload);
// Assert
Assert.IsTrue(canCreate);
Assert.IsFalse(canUpload);
}
This gives you most flexibility when using authorizations for your application and especially useful when you have multiple entities on one screen with different security permissions.
Conclusion
As you can see, there are other points to cover on this topic, but part one is meant to provide a solid understanding on how this technique works and how it's stored.
If you already have an Identity system in place (table-wise), this is all merely C# code to implement this strategy. No database changes necessary. It will load everything for you automatically through Microsoft Identity's Entity Framework classes.
In part two, we'll cover how to associate specific PermissionEnums with Permission entities using C# attributes.
How does your system handle security? Do you use Microsoft Identity or do you use a third-party library? Post your comments below and let's discuss.