Using OWIN Security with MultiValue Data - Part 1

OWIN is the latest framework that .NET developers are using to build ASP.NET applications. OWIN's Identity framework is an evolution of the ASP.NET 2.0 membership system which has been around for several years. It provides better flexibility than the older system, and decouples the Identity framework from IIS.

The intent of OWIN is to create a security framework that can work with ASP.NET applications, even if they aren't running on IIS. This is good news for people developing in Mono for Linux or those creating self-hosting applications.

At its core, OWIN is a middleware model to handle the separation of ASP.NET applications from the actual hosting environment. I will focus mainly on the security and membership side of things but it does much more.

If you are using ASP.NET MVC 6 for your web framework, you will have seen that OWIN is referenced automatically. By default, MVC will assume you are using the traditional ASP.NET identity model, but OWIN's identity model can be extended easily to include access to other identity systems.

When starting a new project, you will see something like the code in Figure 1 the Startup.Auth class. This code is boilerplate, and shows you some of what you can do with the OWIN security. A couple things to point out, the Startup.auth class already includes the built-in providers to handle all the common Social Media logins like Microsoft, Twitter, Facebook and Google. When creating customer-based websites, this is a really nice feature. It also has the code in place to handle Two-Factor logins, so you don't need to build this yourself any more.

Public Sub ConfigureAuth(app As IAppBuilder)
    ' Configure the db context, user manager and signin manager to use a single instance per request
    app.CreatePerOwinContext(AddressOf ApplicationDbContext.Create)
    app.CreatePerOwinContext(Of ApplicationUserManager)(AddressOf ApplicationUserManager.Create)
        app.CreatePerOwinContext(Of ApplicationSignInManager)(AddressOf ApplicationSignInManager.Create)

    ' Enable the application to use a cookie to store information for the signed in user
    ' and to use a cookie to temporarily store inforation about a user logging in with a third party login provider
    ' Configure the sign in cookie
    ' OnValidateIdentity 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.
    Dim _AuthenticationOption As New CookieAuthenticationOptions()
    _AuthenticationOption.LoginPath = New PathString("/Account/Login")
    _AuthenticationOption.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie
    _AuthenticationOption.Provider = New CookieAuthenticationProvider

' Place an expiration on the Login
    '_AuthenticationOption.Provider.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUserStore)(TimeSpan.FromMinutes(30), Function(manager, user)
    '                                                                                                                                                                   user.GenerateUserIdentityAsync(manager)
    '                                                                                                                                                               End Function)

    app.UseCookieAuthentication(_AuthenticationOption)
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie)

    ' Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
    app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5))

    ' Enables the application to remember the second login verification factor such as phone or email.
    ' Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
    ' This is similar to the RememberMe option when you log in.
    app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie)

    ' Uncomment the following lines to enable logging in with third party login providers
    'app.UseMicrosoftAccountAuthentication(
    '    clientId:="",
    '    clientSecret:="")

    'app.UseTwitterAuthentication(
    '   consumerKey:="",
    '   consumerSecret:="")


    'app.UseFacebookAuthentication(
    '   appId:="",
    '   appSecret:="")

    'app.UseGoogleAuthentication(New GoogleOAuth2AuthenticationOptions() With {
    '   .ClientId = "",
    '   .ClientSecret = ""})

End Sub

Figure 1

Another important thing to note is there are references to ApplicationDbContext, ApplicationUserManager, and ApplicationSignInManager in the Startup.Auth class. These classes are instrumental in making the identity system work. Let's look further into these pieces.

ApplicationDbContext

We'll start with the ApplicationDbContext. All this class does is handle the connections to the database and make the stored-procedure (BASIC subroutine) calls. Each of the MultiValue databases have different ways to do this, so the code you will see in < Figure 2 > is very generic.

End Try

        ' Cleanup the connection
        _Connection.Disconnect()
        _Connection = Nothing

        ' Updates the Call Results
        _Results.CallError.Text = _ErrorItem
        _Results.RequestItem.Text = RequestItem
        _Results.Data.Text = UserItem
        Return _Results
    End Function

    ''' <summary>
    ''' Name of the server we are connecting to.
    ''' </summary>
    Public Property ConnectionName As String

#Region "IDisposable Support"
    Private disposedValue As Boolean ' To detect redundant calls

    Protected Sub CheckIfDisposed()
        If disposedValue Then
            Throw New ObjectDisposedException("SpectrumDBContext")
        End If
    End Sub

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not disposedValue Then
            If disposing Then
                '  dispose managed state (managed objects).
            End If

            '  free unmanaged resources (unmanaged objects) and override Finalize() below.
            ' set large fields to null.
        End If
        disposedValue = True
    End Sub

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
        Dispose(True)
    End Sub
#End Region
End Class

Figure 2

This will be the hardest part of any of the coding you will see in these examples, mainly because you have to know how to establish a connection to your MultiValue database with the .NET API offered by your database provider. If you need more examples, you can find them at http://www.intl-spectrum.com/resource/category/44/NET.aspx.

Another issue you may run into is the fact that OWIN is based on the .NET ASYNC/WAIT/Tasks model. This can affect how you code the connection to your MultiValue Database, since many of the APIs don't natively support Async/Wait methods.

The good news is that it is really not very hard to wrap synchronous methods and functions into Task-based processes. If you have not gotten involved with Async/Wait coding yet, now is the time to start looking at it. To see an example of creating an Async/Wait-aware function from a synchronous call, take a look at CallSubroutineAsync.

The main purpose of the code in Figure 2 is to call a subroutine with the following arguments in Figure 3.

SUBROUTINE PROG.NAME(APPLICATION.ITEM, REQUEST.ITEM, DATA.ITEM, NONE1, NONE2, ERROR.ITEM)

Figure 3

I will get into this subroutine in a moment. For now, assume the following:

Information about the application calling this subroutine will be provided in APPLICATION.ITEM.

Request for the database and subroutine to process will be passed in using REQUEST.ITEM.

Information will be returned from the subroutine through DATA.ITEM

Any glaring errors should be returned in ERROR.ITEM

The NONE1 and NONE2 arguments are there for future reference if needed.

I would highly recommend that you always use a single subroutine as the only point of contact whenever you are working with your MultiValue data from outside of the Database. This allows you to keep your business logic where it belongs. It also gives you a choke point for turning off all functions during maintenance. And it offers a single point for audit and security.

ApplicationUser

Before you can create the ApplicationUserManager and ApplicationSignInManager, you will need to create the ApplicationUser class. This class is used to hold information about the user for the rest of the OWIN Identity system to use. It will also contain the main methods to use in order to interact with the database.

This class requires you to implement the IUser(Of String) interface [Figure 4]. The interfaces will require you to implement an Id and UserName property. There are several other properties and methods I would recommend creating in this class as well [Figure 5]:

Imports System.ComponentModel.DataAnnotations
Imports System.Security.Claims
Imports System.Threading.Tasks
Imports Microsoft.AspNet.Identity
Imports Microsoft.AspNet.Identity.Owin
Imports mvOpenAPI.api

Public Class SpectrumUser
  Implements IUser(Of String)

  Dim _Id As String = String.Empty
  Dim _UserItem As New mvDynamicArray

  Public ReadOnly Property Id As String Implements IUser(Of String).Id
      Get
        Return _Id
      End Get
  End Property

  Public Property UserName As String Implements IUser(Of String).UserName
      Get
          Return _UserItem.StringValue(1)
      End Get
      Set(value As String)
          _UserItem.StringValue(1) = value
      End Set
  End Property

...

End Class
  Public ReadOnly Property DBContext As SpectrumDBContext
   Get
    If _DBContext Is Nothing Then
        Try
         _DBContext = HttpContext.Current.GetOwinContext.Get(Of SpectrumDBContext)
        Catch ex As Exception
        End Try
        If _DBContext Is Nothing Then
            _DBContext = SpectrumDBContext.Create
        End If
    End If
    Return _DBContext
   End Get
  End Property
Private _DBContext As SpectrumDBContext

Figure 4

Public Class SpectrumUser
    Implements IUser(Of String)

   ....

    Public Async Function LoadAsync() As Task
        Dim _RequestItem As String = "READ.USER"
        _RequestItem = _RequestItem & mvFunctions.AM & _Id

        Dim _DataItem As String = String.Empty
        Dim _Result As SpectrumDBContext.CallSubroutineResults = Await DBContext.CallSubroutineAsync("SPECTRUM.OWIN.USER", _RequestItem, _DataItem)
        If _Result.CallError.BooleanValue(1, 0, 0) Then
            ' Error, can't find the information
        Else
            ' Extract the User Id and User Item
            Update(_Result.Data.Item(0).Text, _Result.Data.Item(1).Text)
        End If
    End Function

    Public Async Function SaveAsync() As Task
        ' Saves the information to the database
        Dim _RequestItem As String = "WRITE.USER"
        _RequestItem = _RequestItem & mvFunctions.AM & _Id

        ' Send the data to the server
        Dim _DataItem As String = String.Empty
        Dim _Result As SpectrumDBContext.CallSubroutineResults = Await DBContext.CallSubroutineAsync("SPECTRUM.OWIN.USER", _RequestItem, _DataItem)
        If _Result.CallError.BooleanValue(1, 0, 0) Then
            ' Error, can't find the information
        Else
            ' Extract the User Id and User Item
            Update(_Result.Data.Item(0).Text, _Result.Data.Item(1).Text)
        End If
    End Function

  ...

End Class




Figure 5



wSaveAsync - Which will also call the subroutine to handle saving any changes made to the ApplicationUser class.

You will notice in the Figure 5 that the LoadAsync creates a variable REQUEST.ITEM (the actual variable name is _RequestItem) and in REQUEST.ITEM<1> it has a value of "READ.USER" and in REQUEST.ITEM<2> it has the Id of the user we need information for.

One of the nice things about OWIN is that you can assign an arbitrary Id (primary key, record Id) to the user instead of having to translate the UserName every time like you used to have to do in ASP.NET 2.0 membership framework. For obvious reason, this makes pulling information from the database much easier.

SPECTRUM.OWIN.USER Subroutine

Let's take a quick look at the subroutine that we are calling. The first thing we need to do is to decide which request it needs to process [Figure 6]. You will notice that the code actually has three request types, or events, that we want it respond to:

SUBROUTINE SPECTRUM.OWIN.USER(APPLICATION.ITEM,REQUEST.ITEM,DATA.ITEM,NONE1,NONE2,ERROR.ITEM)

...

*
ERROR.ITEM = 0
DATA.ITEM  = ""
BEGIN CASE
  CASE REQUEST.ITEM<1> = "FIND.USER"
    GOSUB 1000
  CASE REQUEST.ITEM<1> = "READ.USER"
    GOSUB 2000
  CASE REQUEST.ITEM<1> = "WRITE.USER"
    GOSUB 3000
  CASE 1
    ERROR.ITEM = 2 :AM: REQUEST.ITEM<1> :" not implemented"
END CASE
*
RETURN

...

2000 * Read User Information
*
ID = REQUEST.ITEM<2>

SUBROUTINE SPECTRUM.OWIN.USER(APPLICATION.ITEM,REQUEST.ITEM,DATA.ITEM,NONE1,NONE2,ERROR.ITEM)

...

*
ERROR.ITEM = 0
DATA.ITEM  = ""
BEGIN CASE
  CASE REQUEST.ITEM<1> = "FIND.USER"
    GOSUB 1000
  CASE REQUEST.ITEM<1> = "READ.USER"
    GOSUB 2000
  CASE REQUEST.ITEM<1> = "WRITE.USER"
    GOSUB 3000
  CASE 1
    ERROR.ITEM = 2 :AM: REQUEST.ITEM<1> :" not implemented"
END CASE
*
RETURN

...

2000 * Read User Information
*
ID = REQUEST.ITEM<2>
READ USER.ITEM FROM OWIN.USER.FILE, ID ELSE
  ERROR.ITEM = 1 :@AM: id :" is not a valid user."
  RETURN
END
*
DATA.ITEM<1> = USER.ITEM<1> ;* UserName
DATA.ITEM<2> = USER.ITEM<2> ;* Password
DATA.ITEM<3> = USER.ITEM<3> ;* Email

...

*
RETURN

...

3000 * Write User Information
*
ID = REQUEST.ITEM<3>
READU USER.ITEM FROM OWIN.USER.FILE, ID LOCKED
  ERROR.ITEM = 3 :@AM: "Record is currently locked by another process."
  RETURN
END ELSE
  USER.ITEM = ""
END
*
USER.ITEM<1> = DATA.ITEM<1> ;* UserName
USER.ITEM<2> = DATA.ITEM<2> ;* Password
USER.ITEM<3> = DATA.ITEM<3> ;* Email

...

*
RETURN
READ USER.ITEM FROM OWIN.USER.FILE, ID ELSE
  ERROR.ITEM = 1 :@AM: id :" is not a valid user."
  RETURN
END
*
DATA.ITEM<1> = USER.ITEM<1> ;* UserName
DATA.ITEM<2> = USER.ITEM<2> ;* Password
DATA.ITEM<3> = USER.ITEM<3> ;* Email

...

*
RETURN

...

3000 * Write User Information
*
ID = REQUEST.ITEM<3>
READU USER.ITEM FROM OWIN.USER.FILE, ID LOCKED
  ERROR.ITEM = 3 :@AM: "Record is currently locked by another process."
  RETURN
END ELSE
  USER.ITEM = ""
END
*
USER.ITEM<1> = DATA.ITEM<1> ;* UserName
USER.ITEM<2> = DATA.ITEM<2> ;* Password
USER.ITEM<3> = DATA.ITEM<3> ;* Email

...

*
RETURN
Figure 6

The READ.USER event is to read the OWIN user information from the database, and return it back to the ApplicationUser Class. I have not gone into the full structure of the DATA.ITEM dynamic array yet.

The WRITE.USER event is to take the information from the ApplicationClass and place it back into the database.

The last event is the FIND.USER. This event will be used to find the record to use when someone first logs in.

The DATA.ITEM will have around 20 attributes that will be used by the ApplicationUser class. As you look at the dynamic array definition I just provided, you will see there are references to two-factor authentication, claims, roles, and external providers. I will talk more about these pieces of data in later articles.

DATA.ITEM<1> = User Name
DATA.ITEM<2> = Password
DATA.ITEM<3> = Email
DATA.ITEM<5> = Cell
DATA.ITEM<7> = Security Stamp
DATA.ITEM<8> = TwoFactor Enabled
DATA.ITEM<9> = Lockout
DATA.ITEM<10> = Lockout Offset
DATA.ITEM<11> = Access Failed Count
DATA.ITEM<12> = First Name
DATA.ITEM<13> = Last Name
DATA.ITEM<14> = Company Name
DATA.ITEM<15,l> = External Login Provider
DATA.ITEM<16,l> = External Login Token
DATA.ITEM<17,ccnt> = Claim Field Name
DATA.ITEM<18,ccnt> = Claim Field Value
DATA.ITEM<19,rcnt> = Roles

Conclusion

This may seem like a lot of work, but it is really just building a set of code that can be used over and over again. This first article covered much of the hard part, but there are a few more pieces that you will need to make everything connect together.

I will continue showing those other components and how it all connects together in upcoming articles in the series.

NATHAN RECTOR

View more articles

Featured:

Jan/Feb 2018

menu
menu