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.

Nathan Rector

Nathan Rector, President of International Spectrum, has been in the MultiValue marketplace as a consultant, author, and presenter since 1992. As a consultant, Nathan specialized in integrating MultiValue applications with other devices and non-MultiValue data, structures, and applications into existing MultiValue databases. During that time, Nathan worked with PDA, Mobile Device, Handheld scanners, POS, and other manufacturing and distribution interfaces.

In 2006, Nathan purchased International Spectrum Magazine and Conference and has been working with the MultiValue Community to expand its reach into current technologies and markets. During this time he has been providing mentorship training to people converting Console Applications (Green Screen/Text Driven) to GUI (Graphical User Interfaces), Mobile, and Web. He has also been working with new developers to the MultiValue Marketplace to train them in how MultiValue works and acts, as well as how it differs from the traditional Relational Database Model (SQL).

View more articles

Featured:

Mar/Apr 2018

menu
menu