I'm writing a simple chat application using ASP.NET MVC 5 and SignalR. The application doesn't require any complicated authentication logic. User simply enters their login and enters the system (if there was no such user in the db before, it's created).
My intent was to use Session to hold the logged in user and their information (id from the database and login/username) and write a global filter to check if user is authenticated on each request. I've got some problems with SignalR though. It's not possible to access the Session from the SignalR Hub, while I need it to find out the login of the user who sent the message.
As fas as I found out, it's possible to work with the User.Identity using SignalR's Context. However, in my case Uder.Identity is completely empty. Presumably because I've created the app as 'no authentication' and the mechanism that User.Identity uses to get user data is not aware of my manipulation with session.
The question is, is it possible to elegantly intergate User.Identity into my application and make it aware of the Session? Creating ASP.NET MVC project with individual user accounts creates a mess with stuff like
public AccountController() :
this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
and that's what I don't want to have in my application by any means, since I want to write it as clean as possible and not use any solutions I am not familiar with. I also don't need any external login providers, cookies, etc.
I was thinking about implementing some in-memory storage on my own. However, I would still have to clean this store up at some point of time. I though of cleaning it up when the Session_End event is fired. However, this event will only be fired if there is data in Session which I don't want to have since it would be quite awkward to have standalone in-memory storage and rely on Session events to clean it up and, moreover, to set some data in Session just to make sure Session_End will fire.
Here's the solution I came up with. It's still not as clear as I would like it to be and it uses cookies, so any additions are welcome.
First of all, I had to install Microsoft.AspNet.Identity.Owin package and all its dependencies.
Then I registered my auth as follows:
private void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
This method is then called in Configuration method of Startup.cs file.
In order to work with the authentication, an instance of IAuthenticationManager is required. I inject it into my controller and use Ninject to resolve the dependency
kernel.Bind<IAuthenticationManager>().ToMethod(_ => HttpContext.Current.GetOwinContext().Authentication).InRequestScope();
Here's the Login method of Account controller which user is redirected to when auth is required (thanks to LoginPath in ConfigureAuth method):
[HttpPost]
public ActionResult Login(LoginViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = authenticationService.AuthenticateUser(model.Login);
IdentitySignIn(user.Id, user.Login);
return RedirectToAction("Index", "Home");
}
AuthenticationService is my own class which communicates with the database and performs the login to create or return a user.
IdentitySignIn is declared as follows:
private void IdentitySignIn(int userId, string userLogin)
{
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.PrimarySid, userId.ToString()));
claims.Add(new Claim(ClaimTypes.Name, userLogin));
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties()
{
ExpiresUtc = DateTime.UtcNow.AddDays(200),
IsPersistent = true
}, identity);
}
This method creates a cookie with appropriate info. There is one thing, though. When I check the cookie expiration date, it's not the current date plus 200 days, which is kinda awkward.
SignOut method is quite simple:
public void IdentitySignout()
{
authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
}
So, User.Identity is now accessible in the SignalR hub with the Identity.Name property.
To do: it would be also nice to get access to the Id property via something like User.Identity.Id. As far as I know, it requires implementing custom Principal.
I am also still thinking of implementing some sort of session of my own using cookies to store the session id on client side, though it will definitely take more time than using Identity.
Addition:
in order to get user id, one might use the extension method of IdentityExtensions:
(Inside the Hub)
Context.User.Identity.GetUserId()
In order for this to work, the Claim with the value of user's id should have the type ClaimTypes.NameIdentifier.
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, userId.ToString()));
claims.Add(new Claim(ClaimTypes.Name, userLogin));
Update 2:
Here are some additional links on the subject that greatly helped me. I do not include links to MS guides since they are quite easy to find.
http://leastprivilege.com/2015/07/21/the-state-of-security-in-asp-net-5-and-mvc-6-claims-authentication/
http://weblog.west-wind.com/posts/2015/Apr/29/Adding-minimal-OWIN-Identity-Authentication-to-an-Existing-ASPNET-MVC-Application
Related
I'm trying to add and delete claims (roles in this case) dynamically after a successful login using Identity 2 in Asp.Net 4.5.2. My application has an authentication database which contains the AspNetUsers, AspNetRoles and AspNetUserRoles tables etc and a number of other databases. During the course of a user session my users can switch between the other databases and their current claims (roles) are modified based on which database they are currently using. I'd like, therefore, to add and delete claims throughout the session. This allows me to modify which views the user has access to based on their current authorizations.
I've done many days of research on this in stack overflow and the MS Identity help pages, such as they are, and can find nothing similar to what I'm attempting to do. Based on what I've learned I've been able to add my own new claims but only during the login process, changing them at any other point works for that request but the changes do not persist and are lost by the time the next request comes in.
As far as I can tell when the claims are added during login they are encoded within the session cookie and when I add them at any other point the cookie is not modified. From my current understanding this is happening in the Identity module of the OWIN pipeline. The method where I've successfully added claims is the GenerateUserIdentityAsync method in ApplicationUser (derived from IdentityUser.
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> 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("Customer_ID", Convert.ToString(this.Customer_ID)));
userIdentity.AddClaim(new Claim("LastName", this.LastName));
userIdentity.AddClaim(new Claim("FirstName", this.FirstName));
userIdentity.AddClaim(new Claim(ClaimTypes.Role, "TestRole", null, null, "TestIssuer"));
return userIdentity;
}
Unfortunately, this doesn't help my case because I'm attempting to add/delete the claims outside the OWIN pipeline.
Is what I'm trying to do possible at all or am I going about this entirely the wrong way? I would have thought dynamic modification of authorizations is not that unusual.
Some of the many stack overflow questions I've looked at include:
How to add claims in ASP.NET Identity,
Dynamic User Claims in ASP.NET Identity EF and
ASP.NET Identity and Claims.
None of them quite cover what I'm attempting to do.
In Asp.net :
Adding claims to existing identity seems like a small task to accomplish. But, well, it doesn’t go so easy. We can build a middleware class and try something like shown here.
foreach(var role in user.Roles)
{
var claim = new Claim(newIdentity.RoleClaimType, role.Name);
identity.AddClaim(claim);
}
And in Asp.net core:
There’s the correct way to edit existing identity and it’s called claims transformation. Basically, we have to write a custom class that implements the IClaimsTransformation interface. Documentation doesn’t give much information about it but the most important thing is said – we need to clone the given identity.
In short, here’s how the process goes:
Clone current user identity
Add custom claims
Return cloned identity
public class AddRolesClaimsTransformation : IClaimsTransformation
{
private readonly IUserService _userService; public class AddRolesClaimsTransformation : IClaimsTransformation
{
private readonly IUserService _userService;
public AddRolesClaimsTransformation(IUserService userService)
{
_userService = userService;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// Clone current identity
var clone = principal.Clone();
var newIdentity = (ClaimsIdentity)clone.Identity;
// Support AD and local accounts
var nameId = principal.Claims.FirstOrDefault(c => c.Type ==
ClaimTypes.NameIdentifier || c.Type == ClaimTypes.Name);
if (nameId == null)
{
return principal;
}
// Get user from database
var user = await _userService.GetByUserName(nameId.Value);
if (user == null)
{
return principal;
}
// Add role claims to cloned identity
foreach (var role in user.Roles)
{
var claim = new Claim(newIdentity.RoleClaimType, role.Name);
newIdentity.AddClaim(claim);
}
return clone;
} }
The final thing to do is to register claims transformation with dependency injection in the ConfigureServices() method of the Startup class.
services.AddScoped<IClaimsTransformation, AddRolesClaimsTransformation>();
enter code here
Based on https://gunnarpeipman.com/aspnet-core-adding-claims-to-existing-identity/
In an asp.net5/mvc6 application I use Azure DocumentDB as a user store and authenticate users with CookieAuthentication like this:
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, account.UserName));
var user = new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme));
await _httpContextAccessor.HttpContext.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user);
I would like to persist the cookie so the user does not have to login every time. This is working correctly now. However when i was to delete a user the user will still have the cookie and thus access to the website.
I use for example on the controllers:
[Authorize("PolicyName")]
Policy configured like this:
services.Configure<AuthorizationOptions>(options => {
options.AddPolicy("PolicyName", policy =>
{ /* configuration */ });
});
There will be multiple policies so I'm looking for a central place to do the reauthentication. Is there a way to hook into the Authorize method?
In the cookie options there is a Events class. Within that is the OnValidatePrincipal event. If you override that it gets called every time cookie auth pulls in an identity.
Task ValidateAsync(CookieValidatePrincipalContext context);
Inside this function you can call context.RejectPrincipal() to invalidate the identity.
ASP.NET identity uses this. Identity has the concept of a security stamp. This is embedded as a claim inside the identity. When changes happen the security stamp is changed. A security stamp validator is wired up to OnValidatePrincipal which checks the membership database value and compares it to the database value.
Background
The application I'm working on is running on several different domains, all sharing the same database and IIS process. The user may switch between these domains by clicking a link, and should remain logged in when doing so. To accomplish this, when the switch domain link is clicked I create an entry in the database which contains the current value of the Identity 2 application cookie (named .AspNet.ApplicationCookie by default). The user is then redirected to the new domain, where the value of the cookie is pulled from the database and set on that domain.
This technique is working, but the problem is that logging out on one domain doesn't log the user out on the other domains because the cookie only gets cleared from the domain that the user happens to be on when he logs out.
Question
Is there a way to make the application cookie invalid upon logging out, so that when it's read on the domains where it still exists (presumably when attempting to authorize the request) it will be ignored and removed from that domain, requiring the user to log in again?
I've tried uncommenting and setting up the OnValidateIdentity callback within the CookieAuthentication configuration. It sounds like this might be related to what I want to do but doesn't seem to do anything on its own.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/"),
CookieName = ApplicationCookieKey, // a string constant
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, User, Guid>(
validateInterval: TimeSpan.FromSeconds(1),
getUserIdCallback: ((identity) => { return identity.GetUserId(); } ),
regenerateIdentityCallback: async (manager, user) => await manager.GenerateUserIdentityAsync(user))
},
});
GenerateUserIdentityAsync method:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(User user)
{
// Note the authenticationType must match the one
// defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
I've tried uncommenting and setting up the OnValidateIdentity callback
within the CookieAuthentication configuration. It sounds like this
might be related to what I want to do but doesn't seem to do anything
on its own.
This is only triggered when the SecurityStamp gets updated. Otherwise it does nothing. You can manually trigger it by calling userManager.UpdateSecurityStamp()
var user = UserManager.Find(...);
ClaimsIdentity identity = UserManager.CreateIdentity(
user, DefaultAuthenticationTypes.ApplicationCookie );
var claim1 = new Claim(
ClaimType = ClaimTypes.Country, ClaimValue = "Arctica", UserId = user.Id );
identity.AddClaim(claim1);
AuthenticationManager.SignIn(
new AuthenticationProperties { IsPersistent = true }, identity );
var claim2 = new Claim(
ClaimType = ClaimTypes.Country, ClaimValue = "Antartica", UserId = user.Id );
identity.AddClaim(claim2);
Both claim1 and claim2 are persisted across requests only for the time ClaimsIdentity user is logged in. In other words, when user logs out by calling SignOut(), the two claims are also removed and as such the next time this user logs in, it is no longer a member of these two claims ( I assume the two claims don't exist anymore )
The fact that claim2 is persisted across requests ( even though authentication cookie was already created when claim2 was added to the user ) suggests that claims don't get persisted across requests via authentication cookie, but via some other means.
So how are claims persisted across requests?
EDIT:
1) As far as I can tell, claims of type IdentityUserClaim are never persisted in a cookie?
var user = UserManager.Find(...);
/* claim1 won't get persisted in a cookie */
var claim1 = new IdentityUserClaim
{ ClaimType = ClaimTypes.Country, ClaimValue = "Arctica", UserId = user.Id };
user.Claims.Add(claim1);
ClaimsIdentity identity = UserManager.CreateIdentity(
user, DefaultAuthenticationTypes.ApplicationCookie );
AuthenticationManager.SignIn(
new AuthenticationProperties { IsPersistent = true }, identity );
If my assumption is correct, is the reason why IdentityUserClaim instances aren't persisted in a cookie because it is assumed that these claims should be stored in a DB and as such could in subsequent requests be retrieved from a DB, while claims of type Claim usually aren't stored in a DB and hence why they need to be persisted in a cookie?
2)
If you'd like to have a deeper look how it all works, check out the
source code of Katana Project
I thought Asp.net Identity 2 was not part of the Katana project ( namely, I've seen people asking when will Microsoft release the source code for Asp.Net Identity, even though Katana source code is already available )?!
thank you
Good question. Even made me do a little experiment.
This line:
AuthenticationManager.SignIn(
new AuthenticationProperties { IsPersistent = true }, identity );
Does not set a cookie. Only sets Identity object for the later callback.
Cookie is only set when the control is passed to middleware and some OWIN internal method called Response.OnSendingHeaders.
So your code is just adding claim2 on the identity object that is stored in memory for later user. In theory you can even set claim1 after you have done the AuthenticationManager.SignIn. And it will be persisted in the cookie anyway.
If you try to add a cliam like this in a controller:
public ActionResult AddNonPersistedClaim()
{
var identity = (ClaimsIdentity)ClaimsPrincipal.Current.Identity;
identity.AddClaim(new Claim("Hello", "World"));
return RedirectToAction("SomeAction");
}
This claim won't be set in the cookie and you will not see it in the next request.
If you'd like to have a deeper look how it all works, check out the source code of Katana Project, look on Microsoft.Owin.Security and Microsoft.Owin.Security.Cookies projects. Along with AuthenticationManager in Microsoft.Owin.Net45 project.
Update
To answer your Edit 1 - IdentityUserClaim is indeed persisted into the database and this is the way you can assign persisted claims to the user. You add these on the user through UserManager
await userManager.AddClaimAsync(userId, new Claim("ClaimType", "ClaimValue"));
This creates records in your database table that represents IdentityUserClaim. When next time user is logged in, these claims are read from the database and added to the identity and are available on ClaimsIdentity.Current via property .Claims or by method .HasClaim().
IdentityUserClaim does not do anything else - just way to serialise Claim object into the database. You don't usually access these directly, unless you want to go "bare knuckles" and write to that table yourself, outside of UserManager.
To put it another way - Identity does not set the cookie. OWIN creates the cookie. Have a look on this piece of code:
public async Task SignInAsync(IAuthenticationManager authenticationManager, ApplicationUser applicationUser, bool isPersistent)
{
authenticationManager.SignOut(
DefaultAuthenticationTypes.ExternalCookie,
DefaultAuthenticationTypes.ApplicationCookie,
DefaultAuthenticationTypes.TwoFactorCookie,
DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
DefaultAuthenticationTypes.ExternalBearer);
var identity = await this.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim(ClaimTypes.Email, applicationUser.Email));
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Here Authentication manager is part of OWIN. Identity is part of System.Security.Claims. All that belongs to Identity project is CreateIdentityAsync method - that is basically converts user from the database into ClaimsIdentity with all the persisted roles and claims.
To answer your Edit 2: You are correct, AspNet Identity is not part of Katana project, but Identity uses OWIN (part of Katana) for cookie handling and authorisation. Identity project mostly deals with user/roles/claims persistence and user management, like locking-out, user creation, sending emails with password resetting, 2FA, etc.
What was a surprise for me is that ClaimsPrincipal along with ClaimsIdentity and Claim are part of .Net framework that is available outside of OWIN or Identity. These are used not only in Asp.Net, but in Windows applications. Good thing that .Net now has open-source and you can browse through all these - gives you a better understanding how it all works together. Also if you are doing unit-testing, it is invaluable to know the internals, so you can stub-out all the functionality without using mocks.
If you are using AD authentication and asp core 2.1 or 2.2, there is an option OpenIdConnectOptions when we configure services named ClaimActions, with the help of ClaimActions you can write class [CustomClaimsFactory] which inherits ClaimActions also override its Run method which actually will set the persistent claims, please find code below :
/* startup.cs */ services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ClaimActions.Add(new CustomClaimsFactory(
"userName",
"xxxxx#outlook.com"
));
}
/*CustomClaimsFactory run method*/ public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
{
identity.AddClaim(new Claim(_ClaimType, _ValueType, issuer));
}
I have a controller which is protected by the [Authorize] attribute.
This works very good (I am sent back to login if I am not logged in), but I wish to add some roles to this attribute, I've read that its possible to do something like [Authorize(Roles = "Customer"] but when I do this I am instantly sent back to the login page on my application?
Is this Roles override not working with the new ASP.NET Identity? On my user creation I am adding the user to the by the following code:
var user = new ApplicationUser {UserName = model.Username};
var result = UserManager.Create(user, model.Password);
if (result.Succeeded)
{
UserManager.AddToRole(user.Id, "Customer");
SignIn(user, false);
return RedirectToAction("Done");
}
And according to the database the user is in this role. Why is this not working? Am I missing a configuration or some sort?
I am going to answer my own question.
The reason this was not working (hours of digging around) it was because my context had the following:
Configuration.ProxyCreationEnabled = false;
This made lazyloading disabled and therefore roles not included, when the user was loaded!
So the fix was to enable this or remove the line.
UPDATE: 2015-05-01
This was a bug, fixed in the 2.0.0-alpha1 release. Thus, this workaround is no longer necessary going forward, and the Roles will load regardless of this setting.
Does Identity Owin require LazyLoading?
Create a role like so:
RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new MyDbContext()));
var roleresult = RoleManager.Create(new IdentityRole(roleName));
Then, add a user like so:
var currentUser = UserManager.FindByName(user.UserName);
var roleresult = UserManager.AddToRole(currentUser.Id, "Superusers");
Please let me know if this works for you.
It works fine with AspNet Identity in my case. Are you sure you:
haven't customized Authorization filters or done it right?
haven't reconfigured authentication/authorization in web.config?
have proper entries in AspNet Identity tables: AspNetUsers, AspNetRoles, AspNetUserRoles (the role exists and the user has it)?
Checkout this answer: ASP.NET Identity check user roles is not working
In your case, while checking for the case, compare the case of IdentityRole record and Authorize Attribute. Do not compare with the UserManager.AddToRole(user.Id, "Customer");
i write a sample to test it,it works good.so i think there 2 points
1.you cookie not save to browser
2.you cookie not with a role info
check you cookie, is there a cookie named ".AspNet.ApplicationCookie" (default name)
if not so check you broswer allow write cookie,or the code you write cookie
if exsit ,you can create a class extends
ISecureDataFormat<AuthenticationTicket>
and config
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
TicketDataFormat=new T()
});
new T() is the class
in this class you need do
public string Protect(AuthenticationTicket data)
and
public AuthenticationTicket Unprotect(string protectedText)
it is some thing about serialize
you can set a break point,and check the data,
in data.Identity.Claims (a IEnumerable< Claim>) should have a Claim with your role info