Enhancing the Menu System with Claims

May 31st, 2017

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

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:

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:

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:

This makes our menu system even easier.

Here's the good news:

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.

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.