Introduction
The buzz word �Security� strikes me all the time when I think of designing an
application. According to me, paramount importance should be given to security
of any application, as hackers are always on the move. Security is something an
application should possess and not be a feature.
Microsoft.NET as a technology, has given various models/patterns, towards
implementing security. The patterns can be customized to suit our requirement
and hence is extensible in nature.
Microsoft.NET framework provides certain objects in the name �Principal� and
�Identity� objects which would be our primary area of thought. This article
mainly focuses on security model by adopting �Principal� and �Identity� objects
and proposes a security model for a .NET application.
What are Principal and Identity objects?
Before we proceed any further, it is important for us understand that the
very intent of Principal and Identity objects is to authenticate and authorize
the end user.
In simple terms, authentication is the process by which the identity of the
user is verified and authorization is the process by which access to an
application/feature is granted based on the identity.
Identity:
This object stores information about the user. This object encapsulates the
name of the user being authenticated. We can relate this object to a data store
which stores information (User Name) about a user. Identity objects are of two
types.
WindowsIdentity
: This object encapsulates the
Windows login user name and the type of protocol adopted for authentication by
Windows. Anytime we need to know the Windows login username, WindowsIdentity
objects have the information.
GenericIdentity
: also stores information about a
user, but is used when an application needs to implement custom logon. This
object can be created by specifying the User name (usually accepted via custom
logon screens) and can be propagated across various application tiers for
authorization purposes.
We shall be discussing more in detail about this at a later stage in this
article.
Principal:
This object represents the security context for the running process or the
AppDomain
. Each thread has a context and it holds the
principal object. Principal objects encapsulate Identity and the Role/Group
membership of a user. Role based security can best be implemented with the help
of Principal objects. A Principal object can be created with the help of
identity and role of a user. Principal objects are very handy while implementing
role based authorization for an application.
Principal objects are of two types:
GenericPrincipal
: This object encapsulates the
identity object and the role. While implementing custom logon with role based
authorization, GenericPrincipal
objects are to be
created.
WindowsPrincipal
: also stores identity and the
Windows group membership of the user. This would very much qualify to implement
role based security for an application.
Implementing role based security with Principal objects
The above theory can be well understood by considering a few of the real time
scenarios.
Scenario A: Windows authentication (Identity) with roles available from
database
Assumption
In this scenario, if roles are found for a user in the database, then the
user is a valid user. If a role does not exist for a user in the database, then
the user is anonymous and hence should not get access to any feature in the
system. Also, there would be an entry for a user in the database only if role(s)
are allocated.
In most cases, for an intranet/Windows based application, Windows
authentication is preferred with roles being application specific and existing
in a data store. Under such circumstances, we would be creating a GenericPrincipal
object with Identity as Windows Logon user
and roles picked up from the data store for a specific identity. This amounts to
Windows identity being used for authentication and GenericPrincipal
object built with roles from database used
for authorization.
Meaning, we would retrieve the Windows identity, get the user name from the
identity object, and query the data store for roles of the user. Once we get the
user roles we can construct a GenericPrincipal
object
and the same can be set to the context/thread.
Now the obvious question would be, who would access the context and how does
authorization happen? Let�s consider a hypothetical case wherein we need to
grant access to a feature in the application based on the user role.
Authentication
This would be a seamless login from the user�s point of view. The user�s
identity can be retrieved with the help of the following code:
Windows application
WindowsIdentity.GetCurrent()
method would give us the
identity object of the logon user
Imports System.Security.Principal
Module SecurityModule
Sub Main()
Dim winIdent As WindowsIdentity = WindowsIdentity.GetCurrent()
Console.WriteLine(winIdent.Name())
End Sub
End Module
Web application
By doing the following the identity of the Windows user is obtained
- Set the authentication mode to
Windows
and deny
authorization to anonymous users in Web.Config file.
- Implement the
WindowsAuthentication_OnAuthenticate
event in the Global.asax file. The WindowsAuthenticationEventArgs
argument received from this
event gives the identity of the Windows user.
<!---->
<authentication mode="Windows"/>
<authorization>
<deny users="?" /> <!---->
<allow users="*" /> <!---->
<authorization/>
Global.asax code: The below event is triggered for every request made
on the web application.
Sub WindowsAuthentication_OnAuthenticate(ByVal Source As Object, _
ByVal e As WindowsAuthenticationEventArgs)
Dim userIdentity As String
userIdentity = e.Identity.Name()
End Sub
Authorization
To authorize the users, we got to have roles associated with the identities
and hence build the principal object. For the identity obtained from the above
global.asax event, query the database and retrieve the roles.
Authorization check would be performed in each page of the web application to
grant or deny access to a particular feature/page in the application.
The below code explains the complete cycle involved in authorizing a user in
accessing an application
Windows application
During application start-up (wherever applicable), for the identity, get the
roles from the database accordingly. With the roles and the identity, the
principal object needs to be created which would be accessed throughout the
application. The below code explains the entire process.
Module SecurityModule
Public genPrincipal As GenericPrincipal
Sub Main()
Dim ident As WindowsIdentity = WindowsIdentity.GetCurrent()
Dim roles() As String
roles = <retrieve from database>
genPrincipal = New GenericPrincipal(ident, roles)
End Sub
End Module
The above generic principal object would be accessed in each of the Windows
forms in deciding whether to grant or deny access to the requested feature, as
shown below:
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
If genPrincipal.IsInRole("Managers") = False Then
MessageBox.Show("You do not have access to this form")
End If
End Sub
Web application
For a web application, before we proceed with authentication check, we got to
build the principal object. And, for the web page to access this principal
object, the same has to be persisted in the HTTP context. The code below
explains this process:
In WindowsAuthentication_OnAuthenticate
event, get
the roles for the identity obtained. This completes the authentication bit. The
next event to get fired just after this is the Application_AuthenticateRequest
wherein the principal object
needs to be built. Hence the identity and the roles obtained in WindowsAuthentication_OnAuthenticate
event have to be
persisted for Application_AuthenticateRequest
to build
the principal object. The same is performed by creating an authentication ticket
out of identity-roles, and send as cookie. Now the Application_AuthenticateRequest
event can access the cookie
and build the Principal object as shown below
Sub WindowsAuthentication_OnAuthenticate(ByVal Source As Object, _
ByVal e As WindowsAuthenticationEventArgs)
Dim userIdentity As String
Dim userRoles As String
Dim formsAuthTicket As FormsAuthenticationTicket
Dim httpcook As HttpCookie
Dim encryptedTicket As String
userIdentity = e.Identity.Name()
useRoles =<Retrieve from Database>
formsAuthTicket = New FormsAuthenticationTicket(1, _
e.Identity.Name(), _
DateTime.Now,DateTime.Now.AddMinutes(60), _
False, userRoles)
encryptedTicket = FormsAuthentication.Encrypt(formsAuthTicket)
httpcook = New HttpCookie("authCookie", encryptedTicket)
Response.Cookies.Add(httpcook)
End Sub
Sub Application_AuthenticateRequest(ByVal sender As Object, _
ByVal e As EventArgs)
Dim formsAuthTicket As FormsAuthenticationTicket
Dim httpcook As HttpCookie
Dim genIdentity As GenericIdentity
Dim roles() As String
Dim genPrincipal As GenericPrincipal
httpcook = Context.Request.Cookies("authCookie ")
formsAuthTicket = FormsAuthentication.Decrypt(httpcook.Value)
genIdentity = New GenericIdentity(formsAuthTicket.Name())
roles = formsAuthTicket.UserData.Split("|")
genPrincipal = New GenericPrincipal(genIdentity, roles)
HttpContext.Current.User = genPrincipal
End Sub
Now after the Principal object has been set, let�s move over to the web page
to authorize the user. In the Form_Load
event of the web
page (PageA.aspx), let�s say, we have to grant access only to users of
role �Managers�. The below code would perform the authorization check
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Dim genPrincipal As IPrincipal
genPrincipal = HttpContext.Current.User
If genPrincipal.IsInRole("Managers") = False then
Response.Write("You do not have access to this Page.")
End if
End Sub
The above would not give access to PageA.aspx, to any user who does
not belong to �Managers� role/group.
In all, this security model best suits for applications which need to
implement role based security with roles as defined by the application and
authentication performed by Windows.
Scenario B: Custom authentication (Identity) with roles available from
database
This scenario best suits applications which have users as well as roles
defined in the application. Such applications will have logon page/form which
accepts username and password and performs authentication. The same code as
explained in scenario A except for authentication mechanism can be adopted in
this scenario as well. The authentication would follow with user ID and password
being validated against a data store and continues to build the generic
principal object with the help of GenericIdentity
and
roles retrieved from the database.
Scenario C: Windows authentication (Identity) with Windows group membership
as roles
This scenario best suits applications which depend on Windows for identity
and roles. Identity would be the Windows username and the role would be the
group membership of the Windows user. The concept of role based security remains
the same and is similar to the code as mentioned in Scenario A. Instead of
creating GenericPrincipal
object, we got to be creating
WindowsPrincipal
object, by supplying the WindowsIdentity
. Also, there is no need to get �roles� from a
data store as we are depending on Windows group membership for roles. The check
that would be performed in each page/form remains the same.
Scenario analysis
In the above scenarios, all we are doing is building the principal objects
which consists of identity and roles. The pages/forms need not worry about
building the Principal objects. They just need to perform �IsInRole� checks
depending on the authorization rules as set by the application.
This demonstrates the clear segregation between authentication and
authorization when Identity and Principal objects are adopted for implementing
security. Also, throughout the life of the application, we are dealing with
objects encapsulating identity, roles and are persisted across.
Conclusion
The scenarios just demonstrate the usage of Principal and Identity objects.
They do not imply any rules on its usage. The scenarios were chosen as a means
to understand the concepts better. In all, the Principal and Identity objects
provide a means to implement security for any application in .NET.
Using the code
The code snippets mentioned throughout this article is part of the source
code available for download with this article. On downloading the source code
(Zip File), extract the same to web folder (wwwroot) to successfully open
the solution. In the IIS Settings for the web application, make sure anonymous
access is unchecked in Directory Security tab.