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 ... * RETURNFigure 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.