I've got a legacy application I'm trying to update to use MVC 5 with Owin authentication. However I've got stuck trying to get SecurityStamp validation to work correctly. I'm probably missing some fundamental piece of the puzzle, but I can't figure out what it is.
Firstly I'm not using Entity Framework. I have existing user tables, that I don't really want to modify too much, as they're used in a lot of legacy code.
So far I have the following...
Public Class SPUser
Implements IUser(Of String)
...
Public Class SPUserStore
Implements IUserStore(Of SPUser)
Implements IUserPasswordStore(Of SPUser)
Implements IUserRoleStore(Of SPUser)
Implements IUserSecurityStampStore(Of SPUser)
...
Public Class SPUserManager
Inherits UserManager(Of SPUser)
...
In my Owin startup code, I have the following...
app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
.LoginPath = New PathString(url.Action("login", "auth")),
.ExpireTimeSpan = TimeSpan.FromMinutes(10),
.SlidingExpiration = True,
.Provider = New CookieAuthenticationProvider() With {
.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(
validateInterval:=TimeSpan.FromMinutes(2),
regenerateIdentityCallback:=Function(manager As SPUserManager, user As SPUser)
Return manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie)
End Function,
getUserIdCallback:=Function(claimsIdentity)
Return claimsIdentity.FindFirstValue(ClaimTypes.Sid).ToString
End Function)
}
My user authentication works fine, my auth controller is able to login the users etc. However where I'm getting stuck is after validateInterval (2 minutes at the moment) the user is automatically kicked off.
I understand that it needs to check the SecurityStamp. In my SPUserStore I've implemented the following method...
Public Function GetSecurityStampAsync(user As SPUser) As Task(Of String) Implements IUserSecurityStampStore(Of SPUser, String).GetSecurityStampAsync
Return Task.Run(Function()
Return user.Id
End Function)
End Function
My plan at the moment is just to get the securitystamp validation to always pass, and then later I'll change it to create the securitystamp from the last modified timestamp for the user table.
In the debugger I can see that this function is called after 2 minutes, however it always fails validation. When Owin calls this function to get the current securitystamp, what should it be comparing it to?
In my SPUserManager class I have the following function to create the claims identity...
Public Overrides Function CreateIdentityAsync(user As SPUser, authenticationType As String) As Task(Of ClaimsIdentity)
Return Task.Run(Function()
Dim claims As New List(Of Claim)
claims.Add(New Claim(ClaimTypes.Sid, user.Id.ToString))
claims.Add(New Claim(ClaimTypes.NameIdentifier, user.UserName))
claims.Add(New Claim(ClaimTypes.GivenName, user.FirstName))
claims.Add(New Claim(ClaimTypes.Surname, user.LastName))
Dim identity As New ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie)
Return identity
End Function)
End Function
There is nothing in there about setting the initial securitystamp, and I'm guessing there should be, but I don't know how to do it. There doesn't seem to be any relevant property for this either on IUser, or ClaimsIdentity.
I really don't know what I'm doing as this is the first time I've tried to implement Asp.net Identity based authentication and the first time I've used MVC. Any help would be greatly appreciated. I can include whatever other code might be relevant.
My apologies for the VB.Net. It seems nobody uses VB.Net for this kind of thing, but I'm kind of stuck with it, as there is too much existing legacy code to convert. C# code samples are fine.
You are indeed missing SecurityStamp on your user. Though you have IUserSecurityStampStore on your SPUserStore class.
Default SecurityStampValidator takes SecurityStamp value for the user from IUserSecurityStampStore.GetSecurityStampAsync(userId). And compare the value to the value stored in the cookie in a Claim of type AspNet.Identity.SecurityStamp. And if the values do not match, your user is getting kicked out.
So if you don't yet have SecurityStamp implemented on the user, then turn off SecurityStampValidtor.
Or make sure you put a random value (by default it is a guid) into user.SecurityStamp in your database. And when you create ClaimsIdentity in CreateIdentityAsync make sure you add another claim:
claims.Add(New Claim(Constants.DefaultSecurityStampClaimType, user.SecurityStamp))
You can look on SecurityStampValidator source code here and just follow what is happening there to avoid your user being logged out for a wrong reason.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With