How should I handle ClaimsPrincipal in unit testing? - c#

I have a controller that checks the claims to get the current user's username, role, like so:
var identity = ClaimsPrincipal.Current.Identities.First();
bUsername = identity.Claims.First(c => c.Type == "Username").Value;
role = identity.Claims.First(c => c.Type == "Role").Value;
These details are set when the user first logs in. When I try to test this method, if falls over at the lines of code above because there was no login. So I tried creating a principal in the TestInitialization so it would always be available for the controllers asking for the username in the above way:
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, "Test Name"),
new Claim(ClaimTypes.NameIdentifier, "test"),
new Claim("Username", "test"),
new Claim("Role","ADMIN")
};
var identity = new ClaimsIdentity(claims, "TestAuthType");
var claimsPrincipal = new ClaimsPrincipal(identity);
I breakpointed the code, and it hits this code before it hits the above code, so it seems like it is creating the principal before checking for the username, however it still falls over on the username check, it's still not finding anything.
Can anybody advise me on what I'm doing wrong?

So, based on the advice of #DavidG, I ended up abstracting the claim stuff out of the controller. Posting this here for anyone else that needs help in the future. I created the following classes:
ICurrentUser
public interface ICurrentUser
{
User GetUserDetails();
}
CurrentUser
public class CurrentUser : ICurrentUser
{
public User GetUserDetails()
{
var user = new User();
try
{
var identity = ClaimsPrincipal.Current.Identities.First();
user.Username = identity.Claims.First(c => c.Type == "Username").Value;
user.Role = identity.Claims.First(c => c.Type == "Role").Value;
user.LoggedIn = identity.Claims.First(c => c.Type == "LoggedIn").Value;
return user;
}
catch (Exception)
{
return user;
}
}
}
Then I added it to my constructor like so:
public readonly ICurrentUser currentUser;
public readonly IUnitOfWork unitOfWork;
public HomeController(IUnitOfWork unitOfWork, ICurrentUser currentUser)
{
this.unitOfWork = unitOfWork;
this.currentUser = currentUser;
}
Then I use Ninject to inject it:
kernel.Bind<ICurrentUser>().To<CurrentUser>().InRequestScope();
Now it is loosely coupled to my controller, allowing me to mock it, like so:
[TestClass]
public class HomeControllerTest
{
private Mock<IUnitOfWork> _unitOfWorkMock;
private Mock<ICurrentUser> _currentUserMock;
private HomeController _objController;
[TestInitialize]
public void Initialize()
{
_unitOfWorkMock = new Mock<IUnitOfWork>();
_currentUserMock = new Mock<ICurrentUser>();
_currentUserMock.Setup(x => x.GetUserDetails()).Returns(new User { Username = "TEST", Role = "ADMIN", LoggedIn = "Y" });
_objController = new HomeController(_unitOfWorkMock.Object, _currentUserMock.Object);
}
}

Related

How to mock AuthenticationStateProvider

I have a class that takes an AuthenticationStateProvider in the constructor. I'd like to write unit test and mock this.
I'd like to be able to set what user is returned from the call GetAuthenticationStateAsync.
const string userId = "123";
const string userName = "John Doe";
const string email = "john.doe#test.com";
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, email),
};
var identity = new Mock<ClaimsIdentity>(claims);
var principal = new Mock<ClaimsPrincipal>(identity.Object);
var mockOfAuthenticationStateProvider = new Mock<AuthenticationStateProvider>();
var mockOfAuthState = new Mock<AuthenticationState>();
mockOfAuthenticationStateProvider.Setup(p =>
p.GetAuthenticationStateAsync()).Returns(Task.FromResult(mockOfAuthState.Object));
I get this errormessage:
Testfunction threw exception: System.ArgumentException: Can not
instantiate proxy of class:
Microsoft.AspNetCore.Components.Authorization.AuthenticationState.
Could not find a parameterless constructor. (Parameter
'constructorArguments') ---> System.MissingMethodException:
Constructor on type 'Castle.Proxies.AuthenticationStateProxy' not
found.
AuthenticationStateProvider is abstract, so if you can't mock it you can create an implementation of it, like this:
public class FakeAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ClaimsPrincipal _principal;
public FakeAuthenticationStateProvider(ClaimsPrincipal principal)
{
_principal = principal;
}
// This static method isn't really necessary. You could call the
// constructor directly. I just like how it makes it more clear
// what the fake is doing within the test.
public static FakeAuthenticationStateProvider ForPrincipal(ClaimsPrincipal principal)
{
return new FakeAuthenticationStateProvider(principal);
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
return Task.FromResult(new AuthenticationState(_principal));
}
}
You can set it up like this:
const string userId = "123";
const string userName = "John Doe";
const string email = "john.doe#test.com";
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, email),
};
// These don't need to be mocks. If they are the test likely
// won't behave correctly.
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
var authenticationStateProvider =
FakeAuthenticationStateProvider.ForPrincipal(principal);
and then pass it to any other class that depends on AuthenticationStateProvider.
When we create a fake implementation instead of a mock it's reusable and also easier to set up, so it keeps the test a little bit smaller.
For example, if the reason you're setting this up is so that the mock/fake returns a user with certain claims, you could have the fake just take a set of claims in its constructor. Then the constructor does the rest of the work, making your test even smaller.
For example,
public class FakeAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ClaimsPrincipal _principal;
public FakeAuthenticationStateProvider(ClaimsPrincipal principal)
{
_principal = principal;
}
public static FakeAuthenticationStateProvider ForPrincipal(ClaimsPrincipal principal)
{
return new FakeAuthenticationStateProvider(principal);
}
public static FakeAuthenticationStateProvider ThatReturnsClaims(params Claim[] claims)
{
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
return new FakeAuthenticationStateProvider(principal);
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
return Task.FromResult(new AuthenticationState(_principal));
}
}
Now your test can have less clutter:
var claims = new []
{
new Claim(ClaimTypes.NameIdentifier, "123"),
new Claim(ClaimTypes.Name, "John Doe"),
new Claim(ClaimTypes.Email, "john.doe#test.com"),
};
var authenticationStateProvider = FakeAuthenticationStateProvider.ThatReturnsClaims(claims);
I usually end up with a folder called "Fakes" in my test project for them.

Client specific role based authentication?

Currently, I am authenticating users in my application using role based authentication with OAuth and WebApi. I've set this up like so:
public override async Task GrantResourceOwnerCredentials (OAuthGrantResourceOwnerCredentialsContext context)
{
var user = await AuthRepository.FindUser(context.UserName, context.Password);
if (user === null)
{
context.SetError("invalid_grant", "The username or password is incorrect");
return;
}
var id = new ClaimsIdentity(context.Options.AuthenticationType);
id.AddClaim(New Claim(ClaimTypes.Name, context.UserName));
foreach (UserRole userRole in user.UserRoles)
{
id.AddClaim(new Claim(ClaimTypes.Role, userRole.Role.Name));
}
context.Validated(id);
}
Protecting my API routes with the <Authorize> tag.
I've since, however, run into an issue where my users can hold different roles for different clients. For example:
User A can be associated to multiple clients: Client A and Client B.
User A can have different "roles" when accessing information from either client. So User A may be an Admin for Client A and a basic User for Client B.
Which means, the following example:
[Authorize(Roles = "Admin")]
[Route("api/clients/{clientId}/billingInformation")]
public IHttpActionResult GetBillingInformation(int clientId)
{
...
}
User A may access billing information for Client A, but not for Client B.
Obviously, what I have now won't work for this type of authentication. What would be the best way to set up Client specific Role based authentication? Can I simply change up what I have now, or would I have to set it up a different way entirely?
You could remove the authorize tag and do the role validation inside the function instead.
Lambda solution:
Are there roles that are added based on CustomerID and UserID?
If so you could do something like the example below where you get the customer based of the values you have and then return the response.
string userID = RequestContext.Principal.Identity.GetUserId();
var customer = Customer.WHERE(x => x.UserID == userID && x.clientId == clientId && x.Roles == '1')
Can you provide us with abit more information about what you use to store the connection/role between the Customer and User.
EDIT:
Here is an example on how you could use the ActionFilterAttribute. It gets the CustomerId from the request and then takes the UserId of the identity from the request. So you can replace [Authorize] with [UserAuthorizeAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class UserAuthorizeAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
try
{
var authHeader = actionContext.Request.Headers.GetValues("Authorization").First();
if (string.IsNullOrEmpty(authHeader))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Authorization-Token")
};
return;
}
ClaimsPrincipal claimPrincipal = actionContext.Request.GetRequestContext().Principal as ClaimsPrincipal;
if (!IsAuthoticationvalid(claimPrincipal))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Invalid Authorization-Token")
};
return;
}
if (!IsUserValid(claimPrincipal))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Invalid User name or Password")
};
return;
}
//Finally role has perpession to access the particular function
if (!IsAuthorizationValid(actionContext, claimPrincipal))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Permission Denied")
};
return;
}
}
catch (Exception ex)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Authorization-Token")
};
return;
}
try
{
//AuthorizedUserRepository.GetUsers().First(x => x.Name == RSAClass.Decrypt(token));
base.OnActionExecuting(actionContext);
}
catch (Exception)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
Content = new StringContent("Unauthorized User")
};
return;
}
}
private bool IsAuthoticationvalid(ClaimsPrincipal claimPrincipal)
{
if (claimPrincipal.Identity.AuthenticationType.ToLower() == "bearer"
&& claimPrincipal.Identity.IsAuthenticated)
{
return true;
}
return false;
}
private bool IsUserValid(ClaimsPrincipal claimPrincipal)
{
string userID = claimPrincipal.Identity.GetUserId();
var securityStamp = claimPrincipal.Claims.Where(c => c.Type.Equals("AspNet.Identity.SecurityStamp", StringComparison.OrdinalIgnoreCase)).Single().Value;
var user = _context.AspNetUsers.Where(x => x.userID.Equals(userID, StringComparison.OrdinalIgnoreCase)
&& x.SecurityStamp.Equals(securityStamp, StringComparison.OrdinalIgnoreCase));
if (user != null)
{
return true;
}
return false;
}
private bool IsAuthorizationValid(HttpActionContext actionContext, ClaimsPrincipal claimPrincipal)
{
string userId = claimPrincipal.Identity.GetUserId();
string customerId = (string)actionContext.ActionArguments["CustomerId"];
return AllowedToView(userId, customerId);
}
private bool AllowedToView(string userId, string customerId)
{
var customer = _context.WHERE(x => x.UserId == userId && x.CustomerId == customerId && x.RoleId == '1')
return false;
}
}
Personally I think you need to move away from using the [Authorize] attribute entirely. It's clear that your requirements for authorisation are more complex than that method "out-the-box" was intended for.
Also in the question about I think Authentication and Authorisation are being used interchangably. What we are dealing with here is Authorisation.
Since you are using Identity and claims based authorisation. I would look at doing this "on-the-fly" so to speak. Along with claims you could make use of dynamic policy generation as well as service based Authorisation using IAuthorizationRequirement instances to build up complex rules and requirements.
Going into depth on the implementation of this is a big topic but there are some very good resources available. The original approach (that I have used myself) was orginally detailed by Dom and Brock of IdentityServer fame.
They did a comprehensive video presentation on this at NDC last year which you can watch here.
Based closely on the concepts discussed in this video Jerrie Pelser blogged about a close implementation which you can read here.
The general components are:
The [Authorize] attributes would be replaced by policy generator such as:
public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
private readonly IConfiguration _configuration;
public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options, IConfiguration configuration) : base(options)
{
_configuration = configuration;
}
public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
// Check static policies first
var policy = await base.GetPolicyAsync(policyName);
if (policy == null)
{
policy = new AuthorizationPolicyBuilder()
.AddRequirements(new HasScopeRequirement(policyName, $"https://{_configuration["Auth0:Domain"]}/"))
.Build();
}
return policy;
}
}
And then you would author any instances of IAuthorizationRequirement required to ensure users are authroised properly, an example of that would be something like:
public class HasScopeRequirement : IAuthorizationRequirement
{
public string Issuer { get; }
public string Scope { get; }
public HasScopeRequirement(string scope, string issuer)
{
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
}
}
Dom and Brock then also detail a client implementation that ties all of this together which might look something like this:
public class AuthorisationProviderClient : IAuthorisationProviderClient
{
private readonly UserManager<ApplicationUser> userManager;
private readonly RoleManager<IdentityRole> roleManager;
public AuthorisationProviderClient(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager)
{
this.userManager = userManager;
this.roleManager = roleManager;
}
public async Task<bool> IsInRole(ClaimsPrincipal user, string role)
{
var appUser = await GetApplicationUser(user);
return await userManager.IsInRoleAsync(appUser, role);
}
public async Task<List<Claim>> GetAuthorisationsForUser(ClaimsPrincipal user)
{
List<Claim> claims = new List<Claim>();
var appUser = await GetApplicationUser(user);
var roles = await userManager.GetRolesAsync(appUser);
foreach (var role in roles)
{
var idrole = await roleManager.FindByNameAsync(role);
var roleClaims = await roleManager.GetClaimsAsync(idrole);
claims.AddRange(roleClaims);
}
return claims;
}
public async Task<bool> HasClaim(ClaimsPrincipal user, string claimValue)
{
Claim required = null;
var appUser = await GetApplicationUser(user);
var userRoles = await userManager.GetRolesAsync(appUser);
foreach (var userRole in userRoles)
{
var identityRole = await roleManager.FindByNameAsync(userRole);
// this only checks the AspNetRoleClaims table
var roleClaims = await roleManager.GetClaimsAsync(identityRole);
required = roleClaims.FirstOrDefault(x => x.Value == claimValue);
if (required != null)
{
break;
}
}
if (required == null)
{
// this only checks the AspNetUserClaims table
var userClaims = await userManager.GetClaimsAsync(appUser);
required = userClaims.FirstOrDefault(x => x.Value == claimValue);
}
return required != null;
}
private async Task<ApplicationUser> GetApplicationUser(ClaimsPrincipal user)
{
return await userManager.GetUserAsync(user);
}
}
Whilst this implementation doesn't address your exact requirements (which would be hard to do anyway), this is almost certainly the approach that I would adopt given the scenario you illustrated in the question.
One solution would be to add the clients/user relationship as part of the ClaimsIdentity, and then check that with a derived AuthorizeAttribute.
You would extend the User object with a Dictionary containing all their roles and the clients that they are authorized for in that role - presumably contained in your db:
public Dictionary<string, List<int>> ClientRoles { get; set; }
In your GrantResourceOwnerCredentials method, you would add these as individual Claims with the Client Ids as the value:
foreach (var userClientRole in user.ClientRoles)
{
oAuthIdentity.AddClaim(new Claim(userClientRole.Key,
string.Join("|", userClientRole.Value)));
}
And then create a custom attribute to handle reading the claims value. The slightly tricky part here is getting the clientId value. You have given one example where it is in the route, but that may not be consistent within your application. You could consider passing it explicitly in a header, or derive whatever URL / Route parsing function works in all required circumstances.
public class AuthorizeForCustomer : System.Web.Http.AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var isAuthorized = base.IsAuthorized(actionContext);
string clientId = ""; //Get client ID from actionContext.Request;
var user = actionContext.ControllerContext.RequestContext.Principal as ClaimsPrincipal;
var claim = user.FindFirst(this.Roles);
var clientIds = claim.Value.Split('|');
return isAuthorized && clientIds.Contains(clientId);
}
}
And you would just swap
[Authorize(Roles = "Admin")] for [AuthorizeForCustomer(Roles = "Admin")]
Note that this simple example would only work with a single role, but you get the idea.
The requirement is about having users with different authorizations. Don't feel oblige to strictly match a user permissions/authorizations with his roles. Roles are part of user identity and should not depend from client.
I will suggest to decompose the requirement:
Only Admin user can access Billing domain/subsystem
//Role-based authorization
[Authorize(Roles = "Admin")]
public class BillingController {
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1
User A may access billing information for Client A, but not for Client B.
Attribute could not work here because we need at least to load the concerned client.
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-2.1&tabs=aspnetcore2x
There is not a built-in way to solve this. You have to define your own merchant access right configuration system.
It might be a simple many to many table (1 user can access N merchants, 1 merchant can be accessed by N users)
public IHttpActionResult GetBillingInformation(int clientId)
{
var merchant = clientRepository.Get(clientId);
if(!UserIsConfiguredToAccessMerchant(User, merchant))
return Unauthorized();
}
NB: Claims should contain only user identity data (name, email, roles, ...). Adding authorizations, access rights claims in the token is not a good choice in my opion:
The token size might increase drastically
The user might have different authorizations regarding the domain
context or the micro service
Below some usefuls links:
https://learn.microsoft.com/en-us/dotnet/framework/security/claims-based-identity-model
https://leastprivilege.com/2016/12/16/identity-vs-permissions/
https://leastprivilege.com/2014/06/24/resourceaction-based-authorization-for-owin-and-mvc-and-web-api/

Moq won't invoke IAuthenticationManager SignIn method

I am trying to run unit tests for a successfull login by verifying that the AuthenticationManager SignIn method has been called once Here's the error I am getting:
Message: Test method Portfolio.UnitTest.WebUI.Controllers.LoginControllerTests.Login_Verified_Authenticates_System_User threw exception:
Moq.MockException:
Expected invocation on the mock once, but was 0 times: m => m.SignIn(AuthenticationProperties, [ClaimsIdentity])
Configured setups:
m => m.SignIn(AuthenticationProperties, [ClaimsIdentity])
Performed invocations:
IAuthenticationManager.SignIn(AuthenticationProperties, [ClaimsIdentity])
Even the error message seems to be contradicting itself.
My controller class/method:
public class LoginController : ProjectBaseController
{
private IAuthenticationManager _authenticationManager;
public IAuthenticationManager AuthenticationManager
{
get
{
return _authenticationManager ?? HttpContext.GetOwinContext().Authentication;
}
set
{
_authenticationManager = value;
}
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Index(LoginViewModel accountUser)
{
if (!ModelState.IsValid)
return View(accountUser);
var systemUser = _authenticationService.Verify(accountUser.Email, accountUser.Password);
if (systemUser == null)
{
ModelState.AddModelError("", "Invalid email address or password");
return View(accountUser);
}
var identity = new ClaimsIdentity
(
new[]
{
new Claim(ClaimTypes.Name, systemUser.FullName),
new Claim(ClaimTypes.Email, systemUser.Email)
},
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name,
ClaimTypes.Role
);
// Set roles for authorization attributes used throughout dashboard
foreach(var role in systemUser.Roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role.Name));
}
AuthenticationManager.SignIn
(
new AuthenticationProperties
{
IsPersistent = true
},
identity
);
return Redirect(accountUser.ReturnUrl);
}
LoginControllerTests:
[TestClass]
public class LoginControllerTests
{
private readonly Mock<IAuthenticationManager> _mockAuthenticationManager;
private readonly Mock<IAuthenticationService> _mockAuthenticationService;
public LoginControllerTests()
{
_mockAuthenticationManager = new Mock<IAuthenticationManager>();
_mockAuthenticationService = new Mock<IAuthenticationService>();
}
private LoginController GetLoginControllerInstance()
{
var controller = new LoginController(_mockAuthenticationService.Object);
controller.AuthenticationManager = _mockAuthenticationManager.Object;
return controller;
}
[TestMethod]
public void Login_Verified_Authenticates_System_User()
{
// Arrange
var viewModel = new LoginViewModel("/")
{
Email = "email#test.co.uk",
Password = "password-test"
};
var systemUser = new SystemUser()
{
Id = new Random().Next(),
FirstName = "Joe",
LastName = "Bloggs",
Email = viewModel.Email,
Password = viewModel.Password,
Roles = new List<Role>()
};
var identity = new ClaimsIdentity
(
new[]
{
new Claim(ClaimTypes.Name, systemUser.FullName),
new Claim(ClaimTypes.Email, systemUser.Email)
},
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name,
ClaimTypes.Role
);
var authenticationProperty = new AuthenticationProperties
{
IsPersistent = true
};
var controller = this.GetLoginControllerInstance();
_mockAuthenticationService.Setup(m => m.Verify(viewModel.Email, viewModel.Password))
.Returns(systemUser);
_mockAuthenticationManager.Setup(m => m.SignIn(authenticationProperty, identity));
// Act
var result = controller.Index(viewModel);
// Assert
Assert.IsNotNull(result);
Assert.IsTrue(controller.ModelState.IsValid);
_mockAuthenticationService.Verify(m => m.Verify(viewModel.Email, viewModel.Password), Times.Once);
_mockAuthenticationManager.Verify(m => m.SignIn(authenticationProperty, identity), Times.Once);
}
}
}
Running in deubg the method gets called within the controller just fine but Moq appears to be ignoring it.
Any ideas?
Nkosi said it in a comment to your question:
In the expression
m => m.SignIn(authenticationProperty, identity)
the two arguments authenticationProperty and identity must be "equal" to the two arguments actually passed in (by the System-Under-Test). So does Equals return true when you compare?
You can use It.IsAny<AuthenticationProperties>() or It.Is((AuthenticationProperties x) => x.IsPersistent) or similar, if needed. And analogous for the ClaimsIdentity. Then Moq will no longer require Equals equality.

How to get HttpContext.Current.User as custom principal in mvc c#

I am trying to use the HttpContext.Current.User with my own class that inherits from IPrincipal. I am not exactly sure why..
var lIdentity = new UserIdentity(lFormsTicket);
var lRoles = lAuthUser.Roles.Select(x => x.Role.ToString()).ToArray();
var lPrincipal = new GenericPrincipal(lIdentity, lRoles);
System.Web.HttpContext.Current.User = lIdentity;
Right after I am setting this it works. But if I do another request on the site, it says that it cannot cast it. Am I missing something here?
You are setting User as IIdentity when it should be IPrincipal
var lIdentity = new UserIdentity(lFormsTicket);
var lRoles = lAuthUser.Roles.Select(x => x.Role.ToString()).ToArray();
var lPrincipal = new GenericPrincipal(lIdentity, lRoles);
System.Web.HttpContext.Current.User = lPrincipal;
You will probably need to use your own custom IPrincipal
public class UserPrincipal : IPrincipal {
string[] roles;
public UserPrincipal (IIdentity identity, param string[] roles) {
this.Identity = identity;
this.roles = roles ?? new string[]{ };
}
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) {
return roles.Any(s => s == role);
}
}
I'm not sure if the application will complain as it may be expecting a ClaimsPrincipal derived type.

How to update a claim in ASP.NET Identity?

I'm using OWIN authentication for my MVC5 project.
This is my SignInAsync
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
var AccountNo = "101";
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim(ClaimTypes.UserData, AccountNo));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, RedirectUri="Account/Index"}, identity);
}
As you can see, i added AccountNo into the Claims list.
Now, how can I update this Claim at some point in my application? So far, i have this:
public string AccountNo
{
get
{
var CP = ClaimsPrincipal.Current.Identities.First();
var Account= CP.Claims.FirstOrDefault(p => p.Type == ClaimTypes.UserData);
return Account.Value;
}
set
{
var CP = ClaimsPrincipal.Current.Identities.First();
var AccountNo= CP.Claims.FirstOrDefault(p => p.Type == ClaimTypes.UserData).Value;
CP.RemoveClaim(new Claim(ClaimTypes.UserData,AccountNo));
CP.AddClaim(new Claim(ClaimTypes.UserData, value));
}
}
when i try to remove the claim, I get this exception:
The Claim
'http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata:
101' was not able to be removed. It is either not part of this
Identity or it is a claim that is owned by the Principal that contains
this Identity. For example, the Principal will own the claim when
creating a GenericPrincipal with roles. The roles will be exposed
through the Identity that is passed in the constructor, but not
actually owned by the Identity. Similar logic exists for a
RolePrincipal.
How does one remove and update the Claim?
I created a Extension method to Add/Update/Read Claims based on a given ClaimsIdentity
namespace Foobar.Common.Extensions
{
public static class Extensions
{
public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return;
// check for existing claim and remove it
var existingClaim = identity.FindFirst(key);
if (existingClaim != null)
identity.RemoveClaim(existingClaim);
// add new claim
identity.AddClaim(new Claim(key, value));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
}
public static string GetClaimValue(this IPrincipal currentPrincipal, string key)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return null;
var claim = identity.Claims.FirstOrDefault(c => c.Type == key);
// ?. prevents a exception if claim is null.
return claim?.Value;
}
}
}
and then to use it
using Foobar.Common.Extensions;
namespace Foobar.Web.Main.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
// add/updating claims
User.AddUpdateClaim("key1", "value1");
User.AddUpdateClaim("key2", "value2");
User.AddUpdateClaim("key3", "value3");
}
public ActionResult Details()
{
// reading a claim
var key2 = User.GetClaimValue("key2");
}
}
}
You can create a new ClaimsIdentity and then do the claims update with such.
set {
// get context of the authentication manager
var authenticationManager = HttpContext.GetOwinContext().Authentication;
// create a new identity from the old one
var identity = new ClaimsIdentity(User.Identity);
// update claim value
identity.RemoveClaim(identity.FindFirst("AccountNo"));
identity.AddClaim(new Claim("AccountNo", value));
// tell the authentication manager to use this new identity
authenticationManager.AuthenticationResponseGrant =
new AuthenticationResponseGrant(
new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = true }
);
}
Another (async) approach, using Identity's UserManager and SigninManager to reflect the change in the Identity cookie (and to optionally remove claims from db table AspNetUserClaims):
// Get User and a claims-based identity
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var Identity = new ClaimsIdentity(User.Identity);
// Remove existing claim and replace with a new value
await UserManager.RemoveClaimAsync(user.Id, Identity.FindFirst("AccountNo"));
await UserManager.AddClaimAsync(user.Id, new Claim("AccountNo", value));
// Re-Signin User to reflect the change in the Identity cookie
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// [optional] remove claims from claims table dbo.AspNetUserClaims, if not needed
var userClaims = UserManager.GetClaims(user.Id);
if (userClaims.Any())
{
foreach (var item in userClaims)
{
UserManager.RemoveClaim(user.Id, item);
}
}
Using latest Asp.Net Identity with .net core 2.1, I'm being able to update user claims with the following logic.
Register a UserClaimsPrincipalFactory so that every time SignInManager sings user in, the claims are created.
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimService>();
Implement a custom UserClaimsPrincipalFactory<TUser, TRole> like below
public class UserClaimService : UserClaimsPrincipalFactory<ApplicationUser, ApplicationRole>
{
private readonly ApplicationDbContext _dbContext;
public UserClaimService(ApplicationDbContext dbContext, UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
{
_dbContext = dbContext;
}
public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
// Get user claims from DB using dbContext
// Add claims
((ClaimsIdentity)principal.Identity).AddClaim(new Claim("claimType", "some important claim value"));
return principal;
}
}
Later in your application when you change something in the DB and would like to reflect this to your authenticated and signed in user, following lines achieves this:
var user = await _userManager.GetUserAsync(User);
await _signInManager.RefreshSignInAsync(user);
This makes sure user can see up to date information without requiring login again. I put this just before returning the result in the controller so that when the operation finishes, everything securely refreshed.
Instead of editing existing claims and creating race conditions for secure cookie etc, you just sign user in silently and refresh the state :)
I get that exception too and cleared things up like this
var identity = User.Identity as ClaimsIdentity;
var newIdentity = new ClaimsIdentity(identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
newIdentity.AddClaims(identity.Claims.Where(c => false == (c.Type == claim.Type && c.Value == claim.Value)));
// the claim has been removed, you can add it with a new value now if desired
AuthenticationManager.SignOut(identity.AuthenticationType);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, newIdentity);
Compiled some answers from here into re-usable ClaimsManager class with my additions.
Claims got persisted, user cookie updated, sign in refreshed.
Please note that ApplicationUser can be substituted with IdentityUser if you didn't customize former. Also in my case it needs to have slightly different logic in Development environment, so you might want to remove IWebHostEnvironment dependency.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using YourMvcCoreProject.Models;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Hosting;
namespace YourMvcCoreProject.Identity
{
public class ClaimsManager
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IWebHostEnvironment _env;
private readonly ClaimsPrincipalAccessor _currentPrincipalAccessor;
public ClaimsManager(
ClaimsPrincipalAccessor currentPrincipalAccessor,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IWebHostEnvironment env)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
_userManager = userManager;
_signInManager = signInManager;
_env = env;
}
/// <param name="refreshSignin">Sometimes (e.g. when adding multiple claims at once) it is desirable to refresh cookie only once, for the last one </param>
public async Task AddUpdateClaim(string claimType, string claimValue, bool refreshSignin = true)
{
await AddClaim(
_currentPrincipalAccessor.ClaimsPrincipal,
claimType,
claimValue,
async user =>
{
await RemoveClaim(_currentPrincipalAccessor.ClaimsPrincipal, user, claimType);
},
refreshSignin);
}
public async Task AddClaim(string claimType, string claimValue, bool refreshSignin = true)
{
await AddClaim(_currentPrincipalAccessor.ClaimsPrincipal, claimType, claimValue, refreshSignin);
}
/// <summary>
/// At certain stages of user auth there is no user yet in context but there is one to work with in client code (e.g. calling from ClaimsTransformer)
/// that's why we have principal as param
/// </summary>
public async Task AddClaim(ClaimsPrincipal principal, string claimType, string claimValue, bool refreshSignin = true)
{
await AddClaim(
principal,
claimType,
claimValue,
async user =>
{
// allow reassignment in dev
if (_env.IsDevelopment())
await RemoveClaim(principal, user, claimType);
if (GetClaim(principal, claimType) != null)
throw new ClaimCantBeReassignedException(claimType);
},
refreshSignin);
}
public async Task RemoveClaims(IEnumerable<string> claimTypes, bool refreshSignin = true)
{
await RemoveClaims(_currentPrincipalAccessor.ClaimsPrincipal, claimTypes, refreshSignin);
}
public async Task RemoveClaims(ClaimsPrincipal principal, IEnumerable<string> claimTypes, bool refreshSignin = true)
{
AssertAuthenticated(principal);
foreach (var claimType in claimTypes)
{
await RemoveClaim(principal, claimType);
}
// reflect the change in the Identity cookie
if (refreshSignin)
await _signInManager.RefreshSignInAsync(await _userManager.GetUserAsync(principal));
}
public async Task RemoveClaim(string claimType, bool refreshSignin = true)
{
await RemoveClaim(_currentPrincipalAccessor.ClaimsPrincipal, claimType, refreshSignin);
}
public async Task RemoveClaim(ClaimsPrincipal principal, string claimType, bool refreshSignin = true)
{
AssertAuthenticated(principal);
var user = await _userManager.GetUserAsync(principal);
await RemoveClaim(principal, user, claimType);
// reflect the change in the Identity cookie
if (refreshSignin)
await _signInManager.RefreshSignInAsync(user);
}
private async Task AddClaim(ClaimsPrincipal principal, string claimType, string claimValue, Func<ApplicationUser, Task> processExistingClaims, bool refreshSignin)
{
AssertAuthenticated(principal);
var user = await _userManager.GetUserAsync(principal);
await processExistingClaims(user);
var claim = new Claim(claimType, claimValue);
ClaimsIdentity(principal).AddClaim(claim);
await _userManager.AddClaimAsync(user, claim);
// reflect the change in the Identity cookie
if (refreshSignin)
await _signInManager.RefreshSignInAsync(user);
}
/// <summary>
/// Due to bugs or as result of debug it can be more than one identity of the same type.
/// The method removes all the claims of a given type.
/// </summary>
private async Task RemoveClaim(ClaimsPrincipal principal, ApplicationUser user, string claimType)
{
AssertAuthenticated(principal);
var identity = ClaimsIdentity(principal);
var claims = identity.FindAll(claimType).ToArray();
if (claims.Length > 0)
{
await _userManager.RemoveClaimsAsync(user, claims);
foreach (var c in claims)
{
identity.RemoveClaim(c);
}
}
}
private static Claim GetClaim(ClaimsPrincipal principal, string claimType)
{
return ClaimsIdentity(principal).FindFirst(claimType);
}
/// <summary>
/// This kind of bugs has to be found during testing phase
/// </summary>
private static void AssertAuthenticated(ClaimsPrincipal principal)
{
if (!principal.Identity.IsAuthenticated)
throw new InvalidOperationException("User should be authenticated in order to update claims");
}
private static ClaimsIdentity ClaimsIdentity(ClaimsPrincipal principal)
{
return (ClaimsIdentity) principal.Identity;
}
}
public class ClaimCantBeReassignedException : Exception
{
public ClaimCantBeReassignedException(string claimType) : base($"{claimType} can not be reassigned")
{
}
}
public class ClaimsPrincipalAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ClaimsPrincipalAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public ClaimsPrincipal ClaimsPrincipal => _httpContextAccessor.HttpContext.User;
}
// to register dependency put this into your Startup.cs and inject ClaimsManager into Controller constructor (or other class) the in same way as you do for other dependencies
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddTransient<ClaimsPrincipalAccessor>();
services.AddTransient<ClaimsManager>();
}
}
}
when I use MVC5, and add the claim here.
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(PATAUserManager manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim(ClaimTypes.Role, this.Role));
return userIdentity;
}
when I'm check the claim result in the SignInAsync function,i can't get the role value use anyway. But...
after this request finished, I can access Role in other action(anther request).
var userWithClaims = (ClaimsPrincipal)User;
Claim CRole = userWithClaims.Claims.First(c => c.Type == ClaimTypes.Role);
so, i think maybe asynchronous cause the IEnumerable updated behind the process.
You can update claims for the current user by implementing a CookieAuthenticationEvents class and overriding ValidatePrincipal. There you can remove the old claim, add the new one, and then replace the principal using CookieValidatePrincipalContext.ReplacePrincipal. This does not affect any claims stored in the database. This is using ASP.NET Core Identity 2.2.
public class MyCookieAuthenticationEvents : CookieAuthenticationEvents
{
string newAccountNo = "102";
public override Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
// first remove the old claim
var claim = context.Principal.FindFirst(ClaimTypes.UserData);
if (claim != null)
{
((ClaimsIdentity)context.Principal.Identity).RemoveClaim(claim);
}
// add the new claim
((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(ClaimTypes.UserData, newAccountNo));
// replace the claims
context.ReplacePrincipal(context.Principal);
context.ShouldRenew = true;
return Task.CompletedTask;
}
}
You need to register the events class in Startup.cs:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddScoped<MyCookieAuthenticationEvents>();
services.ConfigureApplicationCookie(o =>
{
o.EventsType = typeof(MyCookieAuthenticationEvents);
});
}
You can inject services into the events class to access the new AccountNo value but as per the warning on this page you should avoid doing anything too expensive:
Warning
The approach described here is triggered on every request. Validating
authentication cookies for all users on every request can result in a
large performance penalty for the app.
I am using a .net core 2.2 app and used the following solution:
in my statup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
...
})
.AddEntityFrameworkStores<AdminDbContext>()
.AddDefaultTokenProviders()
.AddSignInManager();
usage
private readonly SignInManager<IdentityUser> _signInManager;
public YourController(
...,
SignInManager<IdentityUser> signInManager)
{
...
_signInManager = signInManager;
}
public async Task<IActionResult> YourMethod() // <-NOTE IT IS ASYNC
{
var user = _userManager.FindByNameAsync(User.Identity.Name).Result;
var claimToUse = ClaimsHelpers.CreateClaim(ClaimTypes.ActiveCompany, JsonConvert.SerializeObject(cc));
var claimToRemove = _userManager.GetClaimsAsync(user).Result
.FirstOrDefault(x => x.Type == ClaimTypes.ActiveCompany.ToString());
if (claimToRemove != null)
{
var result = _userManager.ReplaceClaimAsync(user, claimToRemove, claimToUse).Result;
await _signInManager.RefreshSignInAsync(user); //<--- THIS
}
else ...
The simplest solution for updating existing claims for me at the moment was:
//updating user data
await signInManager.SignOutAsync();
await signInManager.SignInAsync(user, false);
Appreciate this question was about .NET 4/ OWIN, but to aid searchers looking for the .NET 5 or later equivalent, here's some sample code.
I'm sure you can improve it, but it's a working starter using the UserManager and SignInManager in Microsoft.AspNetCore.Identity.
// Get the user first first.
var claims = await _userManager.GetClaimsAsync(user);
var givenNameClaim = claims.FirstOrDefault(r => r.Type == JwtClaimTypes.GivenName);
IdentityResult result = null;
if (givenNameClaim != null)
{
result = await _userManager.ReplaceClaimAsync(user, givenNameClaim, new Claim(JwtClaimTypes.GivenName, "<newvalue>"));
}
else
{
result = await _userManager.AddClaimAsync(user, new Claim(JwtClaimTypes.GivenName, "<newvalue>"));
}
if (result.Errors.Any())
{
// TODO: List errors here;
}
else
{
await _signInManager.RefreshSignInAsync(user); // refresh the login, so it takes effect immediately.
}
To remove claim details from database we can use below code. Also, we need to sign in again to update the cookie values
// create a new identity
var identity = new ClaimsIdentity(User.Identity);
// Remove the existing claim value of current user from database
if(identity.FindFirst("NameOfUser")!=null)
await UserManager.RemoveClaimAsync(applicationUser.Id, identity.FindFirst("NameOfUser"));
// Update customized claim
await UserManager.AddClaimAsync(applicationUser.Id, new Claim("NameOfUser", applicationUser.Name));
// the claim has been updates, We need to change the cookie value for getting the updated claim
AuthenticationManager.SignOut(identity.AuthenticationType);
await SignInManager.SignInAsync(Userprofile, isPersistent: false, rememberBrowser: false);
return RedirectToAction("Index", "Home");
Multiple Cookies,Multiple Claims
public class ClaimsCookie
{
private readonly ClaimsPrincipal _user;
private readonly HttpContext _httpContext;
public ClaimsCookie(ClaimsPrincipal user, HttpContext httpContext = null)
{
_user = user;
_httpContext = httpContext;
}
public string GetValue(CookieName cookieName, KeyName keyName)
{
var principal = _user as ClaimsPrincipal;
var cp = principal.Identities.First(i => i.AuthenticationType == ((CookieName)cookieName).ToString());
return cp.FindFirst(((KeyName)keyName).ToString()).Value;
}
public async void SetValue(CookieName cookieName, KeyName[] keyName, string[] value)
{
if (keyName.Length != value.Length)
{
return;
}
var principal = _user as ClaimsPrincipal;
var cp = principal.Identities.First(i => i.AuthenticationType == ((CookieName)cookieName).ToString());
for (int i = 0; i < keyName.Length; i++)
{
if (cp.FindFirst(((KeyName)keyName[i]).ToString()) != null)
{
cp.RemoveClaim(cp.FindFirst(((KeyName)keyName[i]).ToString()));
cp.AddClaim(new Claim(((KeyName)keyName[i]).ToString(), value[i]));
}
}
await _httpContext.SignOutAsync(CookieName.UserProfilCookie.ToString());
await _httpContext.SignInAsync(CookieName.UserProfilCookie.ToString(), new ClaimsPrincipal(cp),
new AuthenticationProperties
{
IsPersistent = bool.Parse(cp.FindFirst(KeyName.IsPersistent.ToString()).Value),
AllowRefresh = true
});
}
public enum CookieName
{
CompanyUserProfilCookie = 0, UserProfilCookie = 1, AdminPanelCookie = 2
}
public enum KeyName
{
Id, Name, Surname, Image, IsPersistent
}
}
if (HttpContext.User.Identity is ClaimsIdentity identity)
{
identity.RemoveClaim(identity.FindFirst("userId"));
identity.AddClaim(new Claim("userId", userInfo?.id.ToString()));
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(HttpContext.User.Identity));
}
Old thread i know, but the requirements seem to have changed.
The below is working for me:
var user = await _userManager.FindByEmailAsync(input.Email);
var userClaim = await _userManager.GetClaimsAsync(user);
var userNameClaims = userClaim.Where(x => x.Type == ClaimTypes.GivenName).ToList();
await _userManager.RemoveClaimsAsync(user, userNameClaims);
await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.GivenName, user.Forename));
await _signInManager.SignOutAsync();
await _signInManager.SignInAsync(user, new AuthenticationProperties() { IsPersistent = input.RememberMe });
The sign out and in methods are essential, without the claims don't reflect the changes
The extension method worked great for me with one exception that if the user logs out there old claim sets still existed so with a tiny modification as in passing usermanager through everything works great and you dont need to logout and login.
I cant answer directly as my reputation has been dissed :(
public static class ClaimExtensions
{
public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value, ApplicationUserManager userManager)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return;
// check for existing claim and remove it
var existingClaim = identity.FindFirst(key);
if (existingClaim != null)
{
RemoveClaim(currentPrincipal, key, userManager);
}
// add new claim
var claim = new Claim(key, value);
identity.AddClaim(claim);
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
//Persist to store
userManager.AddClaim(identity.GetUserId(),claim);
}
public static void RemoveClaim(this IPrincipal currentPrincipal, string key, ApplicationUserManager userManager)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return ;
// check for existing claim and remove it
var existingClaims = identity.FindAll(key);
existingClaims.ForEach(c=> identity.RemoveClaim(c));
//remove old claims from store
var user = userManager.FindById(identity.GetUserId());
var claims = userManager.GetClaims(user.Id);
claims.Where(x => x.Type == key).ToList().ForEach(c => userManager.RemoveClaim(user.Id, c));
}
public static string GetClaimValue(this IPrincipal currentPrincipal, string key)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return null;
var claim = identity.Claims.First(c => c.Type == key);
return claim.Value;
}
public static string GetAllClaims(this IPrincipal currentPrincipal, ApplicationUserManager userManager)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return null;
var claims = userManager.GetClaims(identity.GetUserId());
var userClaims = new StringBuilder();
claims.ForEach(c => userClaims.AppendLine($"<li>{c.Type}, {c.Value}</li>"));
return userClaims.ToString();
}
}
Here you go:
var user = User as ClaimsPrincipal;
var identity = user.Identity as ClaimsIdentity;
var claim = (from c in user.Claims
where c.Type == ClaimTypes.UserData
select c).Single();
identity.RemoveClaim(claim);
taken from here.

Categories