Enhancing the Menu System with Claims

Why make authorization so difficult? In today's post, I update the Menu System to use a simpler technique using Identity.

Written by Jonathan "JD" Danylko • Last Updated: • MVC •

Padlock with a cyber-like background

UPDATE: I've updated the menu system to work with ASP.NET Core 3.1 with MVC and EF Migrations.

Updating the Menu System to ASP.NET Core 3.1 with Microsoft Identity

Based on the response I received from my last post about Integrating Microsoft Identity into a Menu System, I wanted to provide a follow up post to show an easier way of using authorization.

I had a response from a fellow developer (Thanks, Vahid!) saying all I needed to add was a new table called RoleClaim and make those relationships visible in the hierarchy.

While a new table, RoleClaim, would ease the traversing of the menu items, I still felt there was a better approach.

So this got me thinking even more.

If you look at the structure of what I have now, you'll see:

Database Schema of Menu System

AspNetUsers, AspNetUserLogins, AspNetRoles, AspNetUserRoles, and ApplicationUserClaims are all standard Identity tables.

We added AspNetRoleMenu, MenuPermissions, Permissions, and AspNetMenu to work with our Menu System.

I got to thinking...why traverse the whole menu and permission tree when we haven't even used the most obvious choice.

If you look off to the right of the database schema, you'll notice two tables we haven't used yet: UserLogins and...

...The ApplicationUserClaims!

Claims!

THAT's the answer!

The claims aspect of Identity is perfect for this situation because of the following:

  • When I load a user, Identity automatically loads the claims, attaches them to the user, and passes them along. So the next time I need to check authorization, I simply refer to the user and the claims on that user.
  • Based on this simple schema, I want to avoid any additional tables or database hits.

All we need to do is populate the claims for our users.

Time to Refactor!

First, our Menu Manager.

I looked back on this and realized we have an issue with our GetMenuByUser method.

Not only is it a little too big (I'm liking my methods smaller), it has a problem with Roles.

We experience this problem with the Roles when a user has more than one role. Based on the previous code, we were pulling all menu items regardless of permission.

I refactored the GetMenuByUser into two methods which I'll explain later.

Identity/MenuManager.cs

.
.
public
 ICollection<MenuPermission> GetMenuPermissionsByUser(ApplicationUser user) {     if (user == null)     {         return new Collection<MenuPermission>();     }
    return _context.Roles         .Include(role => role.MenuItems.Select(menu => menu.Permissions))         .Where(role => role.Users.Any(tableUser => tableUser.UserId == user.Id))         .SelectMany(role => role.MenuItems.SelectMany(roleMenu => roleMenu.Permissions))         .ToList(); }

public ICollection<MenuItem> GetMenuByUser(ApplicationUser user,     Func<MenuPermission, bool> filterFunc = null) {     var items = GetMenuPermissionsByUser(user);
    var records = filterFunc == null          ? items.ToList()          : items.Where(filterFunc).ToList();
    return records         .GroupBy(menuPermission => menuPermission.RoleMenu.MenuItem)         .Select(grouping => grouping.Key)         .ToList(); }

In the first method, I want to issue eager-loading on the menu items and their associated menu permissions where the user id is in any Role in the table.

Once I have those records, I want to select JUST the MenuPermission records. As you can see from the diagram, they have the most reach. I can grab the menu, role, and permissions in one entity.

Now, our refactored GetMenuByUser method is slimmer.

The whole idea of the GetMenuByUser is to grab all of the menu items by each role. When we pull the menu items for each role, we will have duplicates.

By executing a GroupBy LINQ statement, we return the correct amount of menu items.

And our interface never changed.

Why the GetMenuPermissionsByUser() method?

Glad you asked.

In a more manageable authorization system, you would have a screen to add a user's authorization to various parts of the site.

But for our demonstration, we need to create initial values for our users, Bob and Frank, which is why we need to head over to the ApplicationDbInitializer.

We need to add our seed data for our demo.

This is what our GetMenuPermissionsByUser method assisted in building. We only wanted the menu permissions for populating each user's claims.

In your InitalizeIdentityForEf, append the following lines:

Identity/ApplicationDbInitializer.cs

.
.
// Let's get our updated Ids
var menuManager = new MenuManager(context);
// DevPerms - No, it's not a new developer hairstyle. :-p var devPerms = menuManager.GetMenuPermissionsByUser(devUser);
// Add our claims to each role.
// Developer foreach (var menuPermission in devPerms) {     devUser.Claims.Add(new ApplicationUserClaim     {         UserId = devUser.Id,         ClaimType = menuPermission.RoleMenu.MenuId.ToString(),         ClaimValue = menuPermission.Permission.Name     }); } context.SaveChanges();

// Administrator var adminPerms = menuManager.GetMenuPermissionsByUser(adminUser);
foreach (var menuPermission in adminPerms) {     adminUser.Claims.Add(new ApplicationUserClaim     {         UserId = adminUser.Id,         ClaimType = menuPermission.RoleMenu.MenuId.ToString(),         ClaimValue = menuPermission.Permission.Name     }); } context.SaveChanges();

Our goal here was to add Claims to each user based on their roles so we have an easy way to reference what they can and can't do.

The ApplicationUserClaim is made up of the following properties:

  • UserId - Who is the owner of this Claim?
  • ClaimType - This will be the MenuItem's Id.
  • ClaimValue - What do they have permission to do on this menu item?

This makes our menu system even easier.

Here's the good news:

  • We don't need to change anything in the controller
  • This makes our Menu System even more flexible by adding any permission we want and we merely check the user's claim instead of hitting the database.

Minimal changes for a maximum impact.

If we run our application and log in as Frank, you can see we now have claims to check against our application as to what Frank can and can't do.

Screenshot of Debugging Microsoft Identity with Frank's Claims.

Conclusion

After extending this menu system, we can repurpose and integrate this into any website to provide a robust and simple security mechanism using Identity.

Have you implemented something similar to this? How did you add menus or permissions? Post your comment and let's discuss.

Did you like this content? Show your support by buying me a coffee.

Buy me a coffee  Buy me a coffee
Picture of Jonathan "JD" Danylko

Jonathan Danylko is a web architect and entrepreneur who's been programming for over 25 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Principal Software Engineer Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus