Another question I’ve been asked about Identity.
Part of Startup
class for Owin can be this:
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator
.OnValidateIdentity<UserManager, ApplicationUser, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (Int32.Parse(id.GetUserId())))
},
});
}
The question was “How does SecurityValidator.OnValidateIdentity
invalidate all existing cookies” and “I understand that getUserIdCallback
delegate returns an id of a user, but I don’t quite see the usefulness of this parameter” and “why the need for regenerateIdentityCallback
parameter“.
regenerateIdentityCallback
is a delegate taking UserManager
and ApplicationUser
objects as parameters and returns a new ClaimsIdentity
object. Seems strange to have a separate callback for that because default implementation of UserManager
already has CreateIdentityAsync
which does work for us. But signature of that method looks like this:
public virtual Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
See the authentication type as a parameter? without having a global setting for what type of authentication we use, there is no way know what authentication we use. So part of that delegate somehow should be authentication type.
Regarding getUserIdCallback
– things are simple here. UserId
is stored in cookie as a claim. But it is stored as a string. All claims are stored as strings. If you use default type for the key (String), then no problem here. But if you use Guid
or int
for primary key type, things get more complex. So this peace of code needs to know how to convert String
with id into Guid
or int
.
To answer very first question “How does SecurityValidator.OnValidateIdentity
invalidate all existing cookies“. Well.. it does not really invalidates all the cookies. Cookie contains issued date. On every request SecurityValidator
compares if enough time have passed from cookie issue (30 minutes in the sample code above). Then it gets a UserId
(via getUserIdCallback
delegate) and SecurityStamp
values from the cookie. Reaches into the database, finds the user and checks if SecurityStamp
have been changed since the cookie have been set.
If security stamp is not changed, fresh cookie is created with new issue date. If stamp have been changed, user is logged out.
And this happens on every request. So it could be that user have logged in Firefox, a minute after security stamp have been updated for that user. User will not be logged out from Firefox for another 29 minutes. And imagine that session in Chrome was started 29 minutes before change of security stamp, so cookie in Chrome will be invalidated in 1 minute.
UserManager registration
If you notice regenerateIdentityCallback
is using UserManager
and ApplicationUser
as part of the callback:
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager)
How does it get an instance of UserManager
? OwinContext
is used for that:
var manager = context.OwinContext.GetUserManager<TManager>();
How does OwinContext
know about where to get an instance of UserManager
? Good question!
You must tell OwinContext how to create UserManager:
app.CreatePerOwinContext(() => new UserManager(new MyDbContext()));
Or if you are using DI in your MVC application:
app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<UserManager>());
Or if you are using standard MVC template from Visual Studio 2013:
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
CreatePerOwinContext
function registers a callback in a dictionary. And once this call returns an instance, this instance is saved in a Dictionary<String, object>
for later user. Basically OWIN has it’s own little DI-container where it keeps references to objects that should be singletons during the request.
If cookie validation function can’t get an instance of UserManager
, it will not be able to compare the security stamp in cookie with security stamp in the database and the CookieValidator
won’t be able to invalidate the cookie. So your code should look like this:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(() => new UserManager(new MyDbContext()));
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator
.OnValidateIdentity<UserManager, ApplicationUser, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (Int32.Parse(id.GetUserId())))
},
// other configurations
});
// other stuff
}