11

I just upgraded some of my applications to ASP.NET MVC 5. I've been using ASP.NET Membership for ages and was exploring the possibility of switching to ASP.NET Identity.

I'm building a little test application and I've got authentication working (against active directory and a custom SQL Server schema depending on the user) and even authorization by adding role claims to the ClaimsIdentity before signing the user in (apparently the out of the box Authorize attribute will actually go against any claim information provided).

It's cool that the claims are stored in the authentication cookie by default. However, that poses a problem when information about the user has changed (i.e. they are added or removed from a role).

Obviously I can roll my own Authorize attribute as I've always done before. If I do this I would just skip the claims all together and simply check the roles in the database per request.

Is there any way to use claims in ASP.NET Identity and know when they are no longer valid? Does the framework provide any options to solve this?

Justin Helgerson
  • 24,900
  • 17
  • 97
  • 124
  • Are you worried about revoking access all together (Authentication) or access to specific areas (usually roles / Authorization)? – Erik Philips Feb 12 '14 at 22:53
  • I'm really only concerned with access (authorization). I'd like it if the claims could be invalidated (perhaps a security token change of sorts?). As an example if a user is added to some "Admin" role, I'd like the user to be able to have access to resources that require that role without having to re-authenticate. I see the Identity framework has a `ClaimStore` interface in place that I'm avoiding (including the claim database table which the default Entity Framework implementation seems to use). – Justin Helgerson Feb 12 '14 at 23:52
  • I had the same problem and realized if I re-signin the user, the claims are updated, see [this answer](https://stackoverflow.com/questions/54663462/invalidate-claimsprincipal-after-it-has-been-modified/54681973#54681973) – Hooman Bahreini Feb 14 '19 at 01:34

2 Answers2

4

You probably want to take a look at this question/answer for how this can be taken care of automatically:

What is the SecurityStamp used for?

Community
  • 1
  • 1
Hao Kung
  • 28,040
  • 6
  • 84
  • 93
0

I have recently implemented a claims based feature set in a project.I discovered that Identity does not provide a mechanism to physically update a user claim. The way MS built this it seems that you have to remove a claim from a user so that when the Custom Authorize attributes get hit it will see the user doesn't have the claim and therefore wont pass.

The way I had to build it was to implement a separate Base Claims Utility with views and Controller to manage my custom Claims. For example, when I create a new user I assigned the various claims to the user. I also have an extended or custom Identity manager Class that I interact with to manage my user claims, Passwords, Locking of the account, adding new roles and deleting of roles.

For the view I have created Custom HTML.Helper extension methods to help validate what a user is allowed to have access to or not.

Some code samples follow.

Identity Manager Class

public class IdentityManager
    {
        private RoleManager<IdentityRole> _roleManager;
        private UserManager<ApplicationUser> _userManager;
        private ApplicationDbContext _dbContext;
        private ApplicationSignInManager _signInManager;
        private DpapiDataProtectionProvider protectionProvider;


        public IdentityManager()
        {
            _dbContext = new ApplicationDbContext();
            _roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(_dbContext));
            _userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(_dbContext));
            protectionProvider = new DpapiDataProtectionProvider("Demo");
            _userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(protectionProvider.Create("ResetTokens"));
        }
        public IdentityManager(ApplicationSignInManager signmanager)
        {
            _dbContext = new ApplicationDbContext();
            _signInManager = signmanager;
            _roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(_dbContext));
            _userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(_dbContext));
            protectionProvider = new DpapiDataProtectionProvider("Demo");
            _userManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(protectionProvider.Create("ResetTokens"));
        }

        public ApplicationSignInManager SignInManager
        {
            get
            {
                return _signInManager;
            }
            private set { _signInManager = value; }
        }

        public bool CreateNewUserRole(string role)
        {
            if (!RoleExist(role))
            {
                var result = _roleManager.Create(new IdentityRole(role));

                return result.Succeeded;
            }
            return false;
        }

        public bool DeleteUserRole(string role)
        {
            if (!RoleExist(role))
                return true;

            var result = _roleManager.Delete(new IdentityRole(role));
            return result.Succeeded;
        }

        public IdentityResult DeleteMemberShipUser(ApplicationUser user)
        {
            return _userManager.Delete(user);
        }

        public bool DeleteAllUtilityUsers(int utilityid)
        {
            try
            {
                var users = _dbContext.Users.Where(u => u.UtilityId == utilityid).ToList();

                foreach (var user in users)
                {
                    DeleteMemberShipUser(user);
                }
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public bool RoleExist(string role)
        {
            return _roleManager.RoleExists(role);
        }

        public IdentityResult ChangePassword(ApplicationUser user, string token, string newpassword)
        {
            _userManager.UserValidator = new UserValidator<ApplicationUser>(_userManager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };
            return _userManager.ResetPassword(user.Id, token, newpassword);
        }

        public ApplicationUser GetUserByIdentityUserId(string userId)
        {
            return _userManager.FindById(userId);
        }
        public IdentityResult CreateNewUser(ApplicationUser user, string password)
        {
            _userManager.UserValidator = new UserValidator<ApplicationUser>(_userManager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            _userManager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = false,
                RequireUppercase = false,
            };

            // Configure user lockout defaults
            _userManager.UserLockoutEnabledByDefault = false;
            _userManager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            _userManager.MaxFailedAccessAttemptsBeforeLockout = 5;

            var result = _userManager.Create(user, password);
            return result;
        }

        public IdentityResult UpdateUser(ApplicationUser user)
        {
            return _userManager.Update(user);
        }

        public bool AddUserToRole(string userId, string roleName)
        {
            var result = _userManager.AddToRole(userId, roleName);
            return result.Succeeded;
        }

        public bool RemoveUserFromRole(string userId, string role)
        {
            var result = _userManager.RemoveFromRole(userId, role);
            return result.Succeeded;
        }

        public IList<string> GetUserRoles(string userid)
        {
            return _userManager.GetRoles(userid);
        }

        public string GetUserRole(string userid)
        {
            return _userManager.GetRoles(userid).FirstOrDefault();
        }

        public IdentityRole GetRoleByRoleName(string roleName)
        {
            return _roleManager.Roles.First(i => i.Name == roleName);
        }

        public string GetUserRoleId(string userId)
        {
            var userRole = GetUserRole(userId);
            if (string.IsNullOrWhiteSpace(userRole)) return null;

            var role = GetRoleByRoleName(userRole);
            return role.Id;
        }

        public IdentityResult CreateNewSystemRole(IdentityRole role)
        {
            return !RoleExist(role.Name) ? _roleManager.Create(role) : new IdentityResult(new List<string> { "Role Already Exists" });
        }

        public List<IdentityRole> GetAllRoles()
        {
            return _roleManager.Roles.ToList();
        }

        public bool IsUserInRole(string role, string userName)
        {
            var user = _userManager.FindByName(userName);
            return _userManager.IsInRole(user.Id, role);
        }

        public ApplicationUser GetUserByUserName(string username)
        {
            return _userManager.FindByName(username);
        }

        public string GenerateResetToken(string userid)
        {
            return _userManager.GeneratePasswordResetToken(userid);
        }

        public IdentityResult SetLockStatus(string userid, bool lockstatus)
        {
            return _userManager.SetLockoutEnabled(userid, lockstatus);
        }

        public IdentityResult AddUserClaim(string userId, Claim claim)
        {
            return _userManager.AddClaim(userId, claim);
        }

        public void AddRoleClaim(string roleId, string claimType, string claimValue, int utilityid, string description)
        {
            try
            {
                _userManager.UserValidator = new UserValidator<ApplicationUser>(_userManager)
                {
                    AllowOnlyAlphanumericUserNames = false,
                    RequireUniqueEmail = true
                };

                var roleClaim = new AspNetRoleClaims()
                {
                    RoleId = roleId,
                    ClaimType = claimType,
                    ClaimValue = claimValue,
                    UtilityId = utilityid,
                    Description = description
                };

                _dbContext.AspNetRoleClaims.Add(roleClaim);
                _dbContext.SaveChanges();
            }
            catch (Exception ex)
            {
                throw new IdentityNotMappedException(ex.Message, ex);
            }
        }

        public IList<Claim> GetUserClaims(string userId)
        {
            return _userManager.GetClaims(userId);
        }

        public IdentityResult RemoveUserClaim(string userId, string claimType)
        {
            _userManager.UserValidator = new UserValidator<ApplicationUser>(_userManager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            var claim = _userManager.GetClaims(userId).FirstOrDefault(t => t.Type == claimType);
            if (claim == null) return IdentityResult.Success;

            return _userManager.RemoveClaim(userId, claim);
        }

        public void DeleteRole(string id)
        {
            var language = new LanguageCodeLookup();
            var aspNetRoles = _dbContext.Roles.FirstOrDefault(r => r.Id == id);

            if (aspNetRoles == null)
                throw new Exception(language.RoleDoesNotExist);
            if (aspNetRoles.Name == "Utility Administrator" ||
                aspNetRoles.Name == "Content Manager" ||
                aspNetRoles.Name == "System Administrator" ||
                aspNetRoles.Name == "Customer Accounts Manager")
                throw new Exception(language.CannotDeleteDefaultRoles);
            if (aspNetRoles.Users.Count > 0)
                throw new Exception(language.CannotDeleteRolesWithUsers);
            _dbContext.Roles.Remove(aspNetRoles);
            _dbContext.SaveChanges();
        }

        public IdentityRole GetRole(string id)
        {
            return _dbContext.Roles.FirstOrDefault(r => r.Id == id);
        }
    }

Custom Claims Authorize Attribute

public class ClaimsAuthorizeAttribute : AuthorizeAttribute
    {
        private readonly string _claimType;
        public ClaimsAuthorizeAttribute(string type)
        {
            _claimType = type;
        }
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            var user = (ClaimsPrincipal)HttpContext.Current.User;

            if (user.HasClaim(_claimType, "True"))
            {
                base.OnAuthorization(filterContext);
            }
            else
            {
                HandleUnauthorizedRequest(filterContext, _claimType + " Not Allowed ");
            }
        }

        protected void HandleUnauthorizedRequest(AuthorizationContext filterContext, string message)
        {
            filterContext.Result = new RedirectToRouteResult(
                                       new RouteValueDictionary
                                   {
                                       { "action", "ClaimNotAuthorized" },
                                       { "controller", "Home" },
                                       {"errorMessage", message }
                                   });
        }

        public static bool AuthorizedFor(string claimType)
        {
            var user = (ClaimsPrincipal)HttpContext.Current.User;
            return user.HasClaim(claimType, "True");
        }
    }

Claim Usage

[ClaimsAuthorize(ClaimsData.EditCustomer)]
public ActionResult Index(string customerNo = "", int filterID = 0, int filterStatusID = 0)

View Usage Razor

public static bool AuthorizedFor(this HtmlHelper htmlHelper, string claimType)
    {
        if (!string.IsNullOrEmpty(claimType))
        {
            var user = (ClaimsPrincipal)System.Web.HttpContext.Current.User;
            return user.HasClaim(claimType, "True");
        }
        return false;
    }

Render a HTML String if Claims is Passed

public static MvcHtmlString RenderToastrHiddenInputs(this HtmlHelper htmlHelper, object success, object info, object warning, object error, string claimType)
    {
        if (AuthorizedFor(htmlHelper, claimType))
        {
            var html = string.Format(@"
                <input type='hidden' id='success' value='{0}' />
                <input type='hidden' id='info' value='{1}' />
                <input type='hidden' id='warning' value='{2}' />
                <input type='hidden' id='error' value='{3}' />", success, info, warning, error);

            return new MvcHtmlString(html);
        }

        return null;
    }

Hope all this makes sense :)

Trevor Reid
  • 3,310
  • 4
  • 27
  • 46
Bernardt
  • 65
  • 1
  • 10