Using OWIN Security with MultiValue Data - Part 2
In my last article, I explained a little bit about what OWIN was, and the basic setup of how to create a connection between an OWIN application and a MultiValue framework. This article is mainly focused on using OWIN's identity/security framework.
Quick Review
ApplicationDBContext — This class does all the work of connecting to the database and making the subroutine calls to return or update data.
ApplicationUser — This class is used to hold information about the user, making it available for the rest of the OWIN Identity system. This class will call the subroutine, in out case SPECTRUM.OWIN.USER, and return a dynamic array of information about the user.
ApplicationUserStore
Now that you have the basis for the ApplicationUser class, you have to implement the ApplicationUserStore class. This class does most of the work connecting the data from the subroutine SPECTRUM.OWN.USER, with the rest of the Identity system.
This is where the OWIN Identity System gets a little ugly. Microsoft, in their infinite wisdom, decided to create a very granular identity system. This specifically addresses the biggest complaint about previous frameworks: They were either too cumbersome to implement and user, or too subtle to customize to individual needs.
The OWIN granularity means you don't have to implement everything they want you to, if you don't need it or to want it. The reality though, is that in order to take advantages of what the OWIN identity framework provides, you pretty much have to implement all of it anyway. This really isn't a big issue since the individual interfaces are actually relatively simple to implement. And while annoying to have to type in all this code, it does make it easier to write articles that walk you through it.
Interface IUserStore
The only Interface that you are required to implement is the IUserStore(Of IUser) (See Figure 1). It must have the ability to Create, Update, Delete and Find users.
Public Class ApplicationUserStore Implements IUserStore(Of ApplicationUser) ... Public ReadOnly Property Users As IQueryable(Of ApplicationUser) Implements IQueryableUserStore(Of ApplicationUser, String).Users Get Return _Users End Get End Property Private _Users as New List(Of ApplicationUser) Public Function CreateAsync(user As ApplicationUser) As Task Implements IUserStore(Of ApplicationUser, String).CreateAsync Return user.SaveAsync End Function Public Function UpdateAsync(user As ApplicationUser) As Task Implements IUserStore(Of ApplicationUser, String).UpdateAsync Return user.SaveAsync End Function Public Function DeleteAsync(user As ApplicationUser) As Task Implements IUserStore(Of ApplicationUser, String).DeleteAsync Throw New Exception("User Delete is not supported") End Function Public Function FindByIdAsync(userId As String) As Task(Of ApplicationUser) Implements IUserStore(Of ApplicationUser, String).FindByIdAsync Return FindUserAsync(DBContext, "Id", userId) End Function Public Function FindByNameAsync(userName As String) As Task(Of ApplicationUser) Implements IUserStore(Of ApplicationUser, String).FindByNameAsync Return FindUserAsync(DBContext, "UserName", userName) End Function ... End Class
Public Class ApplicationUserStore Implements IUserStore(Of ApplicationUser) ... Public ReadOnly Property Users As IQueryable(Of ApplicationUser) Implements IQueryableUserStore(Of ApplicationUser, String).Users Get Return _Users End Get End Property Private _Users as New List(Of ApplicationUser) Public Function CreateAsync(user As ApplicationUser) As Task Implements IUserStore(Of ApplicationUser, String).CreateAsync Return user.SaveAsync End Function Public Function UpdateAsync(user As ApplicationUser) As Task Implements IUserStore(Of ApplicationUser, String).UpdateAsync Return user.SaveAsync End Function Public Function DeleteAsync(user As ApplicationUser) As Task Implements IUserStore(Of ApplicationUser, String).DeleteAsync Throw New Exception("User Delete is not supported") End Function Public Function FindByIdAsync(userId As String) As Task(Of ApplicationUser) Implements IUserStore(Of ApplicationUser, String).FindByIdAsync Return FindUserAsync(DBContext, "Id", userId) End Function Public Function FindByNameAsync(userName As String) As Task(Of ApplicationUser) Implements IUserStore(Of ApplicationUser, String).FindByNameAsync Return FindUserAsync(DBContext, "UserName", userName) End Function ... End Class
Figure 1
As you look at this you will notice that seems a little too simple. Well, it is. Sometimes we get lucky. IUserStore does not implement any password handling or role management or any ability to do login or out. This will be handled by the next few interfaces.
Two things that I'd like to highlight [Figure 1], are the FindByIdAsync and FindByNameAsync functions. They provide OWIN with a way to lookup existing users in your database. The ApplicationUser class does not have a way to do these lookup by default, so you have to implement this lookup yourself. I've provided and example to show how I approached it using a subroutine [Figure 2] to help you implement your own solutions.
Public Async Function FindUserAsync(DBContext As ApplicationDBContext, FieldName As String, FieldValue As String) As Task(Of ApplicationUser) ' Create the User Object if not found Dim _User As ApplicationUser = Nothing For I As Integer = 0 To (_Users.Count - 1) If FieldName.ToLower = "id" AndAlso _Users(I).Id = FieldValue Then _User = _Users(I) ElseIf FieldName.ToLower = "username" AndAlso _Users(I).UserName = FieldValue Then _User = _Users(I) ElseIf FieldName.ToLower = "email" AndAlso _Users(I).Email = FieldValue Then _User = _Users(I) End If Next If _User IsNot Nothing Then ' Found an item, so check to see if we need to update it or not. If _User.LastRead.Add(TimeSpan.FromMinutes(10)) > Now Then ' The last time this item has been read was longer than 5 mins ago ' reread current information from database _User.LastRead = Now Else ' ReRead the infomraiton Await _User.LoadAsync() End If ' Returns the user information Return _User Else ' Didn't find a user, so look it update in the database Dim _RequestItem As String = "FIND.USER" _RequestItem = _RequestItem & mvFunctions.AM & FieldName _RequestItem = _RequestItem & mvFunctions.AM & FieldValue ' Send the data to the server Dim _DataItem As String = String.Empty Dim _Result As ApplicationDBContext.CallSubroutineResults = Await DBContext.CallSubroutineAsync("SPECTRUM.OWIN.USER", _RequestItem, _DataItem) If _Result.CallError.BooleanValue(1, 0, 0) Then ' Error, can't find the information Return Nothing ElseIf _Result.Data.Item(0).IsNullOrEmpty Then ' Nothing returned Return Nothing Else ' Extract the User Id and User Item and create ' the User Object _User = New ApplicationUser(_Result.Data.Item(0).StringValue(1, 1), _Result.Data.Item(1)) _Users.Add(_User) Return _User End If End If End Function
Figure 2
Keeping a cached copy of the retrieved information in memory is one of the most important things that we need to be doing each time we access a user from the database. Doing this keeps the application responsive. As we all know, the slowest part of any client server applications is usually the communication with the database itself.
Since this is a memory cached copy, we don't always know how long an item has been in memory and needs to be refreshed. It is always important to keep track of when something is placed in cache so you know when to refresh the information in case it has changed.
I've done this by added an extra property to the ApplicationUser class we built in Part I [Tracey: we need a link to that issue here] called LastRead. Every time an ApplicationUser is read from, or saved to, the database, this property will be set to the current date/time. This allows us to keep track of how old the data is and help us decide whether our application should reload the data or not.
I do this using a ten seconds timeout [ Figure 2 ]. I chose ten seconds for no other reason that it contains the best of both worlds. If the OWIN framework needs to look up and access a ApplicationUser object somewhere else in its pipeline, it is likely less than ten seconds from the initialization of the ASP.NET page.
If the object is older than that, then we must make sure to refresh the information. Otherwise, if an operation updates the database with new roles or passwords, the user would be forced to wait on the web server to decide if the data is old or not.
Interface IUserPasswordStore
Since we normally need to authenticate the user with a password, it is important to implement the IUserPasswordStore interface [ Figure 3 ]. One of the gotchas with OWIN is that it uses Microsoft's default password hashing system. While this is good because it does not keep the passwords as clear text, it doesn't help us when we need to reset a password outside of the OWIN framework.
Public Class ApplicationUserStore Implements IUserStore(Of ApplicationUser) Implements IUserPasswordStore(Of ApplicationUser) ... Public Function GetPasswordHashAsync(user As ApplicationUser) As Task(Of String) Implements IUserPasswordStore(Of ApplicationUser, String).GetPasswordHashAsync Return Task.FromResult(Of String)(user.PasswordHash) End Function Public Function SetPasswordHashAsync(user As ApplicationUser, passwordHash As String) As Task Implements IUserPasswordStore(Of ApplicationUser, String).SetPasswordHashAsync user.PasswordHash = passwordHash Return Task.FromResult(0) End Function Public Function HasPasswordAsync(user As ApplicationUser) As Task(Of Boolean) Implements IUserPasswordStore(Of ApplicationUser, String).HasPasswordAsync Return Task.FromResult(Of Boolean)(Not String.IsNullOrEmpty(user.PasswordHash)) End Function ... End Class
Figure 3
Sometimes you need to keep the password stored as plain text, or in a two-way hash system, in order to use the same password functions already built-in to your LOB (Line Of Business) system. In order to do this, you will need to create another class, outside the ApplicationUserStore class, to handle keeping passwords in clear text [ Figure 4 ].
Imports Microsoft.AspNet.Identity ''' <summary> ''' This Class is used to decide the type of password received from the database ''' and generates a has value to compare it. ''' </summary> Public Class ApplicationPasswordHasher Implements IPasswordHasher Public Function HashPassword(password As String) As String Implements IPasswordHasher.HashPassword ' Return Clear Text as the Hash. Not as secure, but needed when sending password to ' program. Return password End Function Public Function VerifyHashedPassword(hashedPassword As String, providedPassword As String) As PasswordVerificationResult Implements IPasswordHasher.VerifyHashedPassword ' No hash was done, so check clear text. If hashedPassword = providedPassword Then Return PasswordVerificationResult.Success Else Return PasswordVerificationResult.Failed End If End Function End Class
Figure 4
This class will be hooked up to the ApplicationUserStore when we implement the ApplicationUserManager.
Conclusion
These are the only interfaces that you really need to implement in order to make the OWIN Security framework functional. There are many more that I will go into in later articles that I think are also important. These are the minimum that are required to get something to work.