.NET Identity Claims Are Under-utilized

What did I learn today?

Today I realized how I may have been under-utilizing .NET Identity Claims for a long time.  I’ve only really used identity claims under normal circumstances which basically just handled default information about a logged in user; Name, Email, etc.  Until recently this was usually enough. However, I recently have had 2 instances where if I did not extend the default claims, I would need to write an enormous amount of code.  Without using claims, I would either have to write code to pass values between services or constantly look it up from a database.

Today’s post will describe two ways I have recently used .NET Identity Claims to solve problems that would have required a lot of rework and code.

What is Identity Claims?

A claim is basically describing what something is and what it is not.  When we attach identity to this, many people associate a claim with what something can or cannot do.  A claim is merely describing the object. In the case of identity claims, that object is usually a user and claims are describing that user. However, the applications we write should be mapping our object (and its claims) with what it can and cannot do (the object’s roles).

How I’ve Recently Used Claims

Scenario One

Multi-tenant application

Multi-tenant architecture
My MS Paint Skills in action!

On a recent project, I had built a very small, homegrown CMS that was meant for one line of business to manage content for a small mobile application we built.  The application was a huge success and soon other lines of business at the organization wanted their own version.  Instead of copying the codebase and creating multiple instances, that would be hard to manage, I decided to make the application multi-tenanted.

My original thought was to assign a TenantId to each line of business and pass that Id to with every request in to the Web API methods I created.  This would require me to change each and every service method I created to receive and look up that number.  Yuck!  Not exactly what I had in mind.  However, a co-worker pointed me in the direction of using claims to store the TenantId when the user logged in.  Which would prevent me from having to change every Web API call to also pass in the user’s TenantId.

The Code

AppStart.cs – Bolded items are how I get the tenant Id and set the claim.  The most important part is to set the claim after the security token has been validated.  This example is using WSFederation but other authentication types have similar events.

int tenantId = 0;
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
    MetadataAddress = ConfigurationManager.AppSettings["MetaAddress"],
    Wtrealm = ConfigurationManager.AppSettings["Realm"],
    Notifications = new WsFederationAuthenticationNotifications()
    {
        RedirectToIdentityProvider = (ctx) =>
        {
            if (!ctx.OwinContext.Request.Path.Value.Contains("login"))
            {
                 ctx.OwinContext.Response.StatusCode = 403;
                 ctx.HandleResponse();
            }

            var tenant = ctx.Request.Query["TenantId"];
            if (!string.IsNullOrEmpty(tenant))
            {
                 int.TryParse(tenant, out tenantId);
            }

            return Task.FromResult(0);
        },
        SecurityTokenValidated = (ctx) =>
        {
            var tenant = tenantId;
            ctx.AuthenticationTicket.Identity.AddClaim(new Claim("TenantId", tenant.ToString()));
            return Task.FromResult(0);
        },
   },
});

RandomAPIController.cs – Example of me calling a method that retrieves the tenantId out of the claims

public IEnumerable Get()
        {
            
ClaimsIdentity identity = (ClaimsIdentity)User.Identity;
            var tenantId = GetTenantId(identity);

            return CategoryDAL.getCategories(tenantId);
        }

RandomUtilityLibrary.cs – Example of a helper library that actually retrieves the tenantId out of the claims.

      public static int GetTenantId( ClaimsIdentity identity) 
      {
            int tenantId = 0;
            if (identity.Claims.Where(c => c.Type == "TenantId").Count() > 0)
            {
                var tenantClaim = identity.Claims.Where(c => c.Type == "TenantId").First().Value;
                
                //Default tenant in case logged in user does not have one.
                if (!int.TryParse(tenantClaim, out tenantId))
                {
                    tenantId = int.Parse(ConfigurationManager.AppSettings["TenantId"]);
                }
            }

The above scenario allowed me to assign the logged in user’s TenantId when they logged in.  This TenantId was then automatically passed with each request through the user’s claims instead of requiring me to change every single API call in my applications front-end to pass in a TenantId.  This saved me hours of coding and many hours coding to maintain in the future.

Scenario Two

User Impersonation

On another recent project, I had a requirement that admins would have the ability to impersonate other less privileged users in the system.  Not having done this before, I did a lot of research (aka Googling) to see if anyone else has run into this type of requirement.  Lo and Behold!  I ran into two blog posts that were attempting to do what I was trying to do.  Special thanks to The Reformed Programmer and Max Vasilyev for their hard work and the willingness to share their solutions with others.

I won’t copy and paste all of my code on how I accomplished this task.  Max’s blog post contains most of that code but I will show how I modified it.  The last requirement is something that was a tad different from the blogs posted above so I thought I would share.

Requirements:

  • Admin should be able to find users they wish to impersonate in a list
  • Admin should be able to click a button pertaining to that user and enter the section of the system that only the users have access to, viewing that area as that users.
  • Admin will have authority to add, modify, delete content as if they were that user but will not have access to view their impersonated user’s password.
  • When impersonating, a banner should show letting the admin know who they were signed in as, who they are impersonating, and button to return their original screen.
As Admin
This is captured from an AngularJS page.  The edit button calls the method to impersonate the user.
vm.impersonateUser = function (userId, url) {
            var user = { profileObject: userId, requestPath: "admin#/impersonate-user-screen"}
            $http.post('account/ImpersonateUserAsync', user).then(function success() {
                window.location.href = url;
            });
        }

The above code calls the ImpersonateAsync method described in Max’s blog post.  The difference being is that I add a new claim to capture what page the admin user came from (which is bolded)

[HttpPost]
        public async Task ImpersonateUserAsync(User user)
        {
            var context = this.ControllerContext.HttpContext;

            var originalUsername = context.User.Identity.Name;

            var impersonatedUser = await UserManager.FindByEmailAsync(user.ProfileObject.Email);

            var impersonatedIdentity = await UserManager.CreateIdentityAsync(impersonatedUser, DefaultAuthenticationTypes.ApplicationCookie);
            impersonatedIdentity.AddClaim(new Claim("UserImpersonation", "true"));
            impersonatedIdentity.AddClaim(new Claim("OriginalUsername", originalUsername));
            impersonatedIdentity.AddClaim(new Claim("OriginalPath", user.RequestPath));

            var authenticationManager = context.GetOwinContext().Authentication;
            authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
            authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, impersonatedIdentity);
        }
As Impersonated User
This is the impersonated users page. This banner displays at the top when the page is being viewed as an impersonated users. This lets the user know exactly who they are and give them the ability to return to where they came from.

The above banner contains the “Return to Original Screen Button” which calls the RevertImpersonationAsync() method that is described in Max’s Blog

Conclusion

I described two scenarios where I used Claims Identity to accomplish tasks that would have taken far more code if I were to write them differently.  I basically took the claims code for granted and was just blindly copying default login methods from StackOverflow and MSDN without really thinking of the power available in the existing framework.  In the future, I plan on looking at claims as a method to store non-identifying information instead of reading it from a database or passing it around every request.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s