Using Windows Authentication and Authorization in your Application

Dotnetspy is designed to share some real life programming practices and examples with the programmer's society. This article is a starter for the windows programmers who want to use window's inbuilt features like Authentication and Authorization in their application. Please send your feedback or suggestions at webmaster@dotnetspy.com

Introduction

Windows provides some very good inbuilt features that may be used by windows programmers to decrease the programming efforts, enhance their application's performance and certainly without compromising the application security level. We are going to discuss one of them which is Authentication and Authorization. There are several advantages of using windows authentication and authorization:

Authentication and Authorization described

Microsoft defines "Authentication is the process of discovering and verifying the identity of a principal by examining the user's credentials and validating those credentials against some authority. The information obtained during authentication is directly usable by your code". After authenticating a principal/user, "the next step is to determine whether that principal has permission to access the resources it is requesting. This process is known as Authorization." 

In simple words, Authentication is a process by which your application/code identifies a user as a valid application user. After the user is identified as an authenticated user, application, now, should provide access to the resources for which user is authorised. Let us understand it as when you use mail on www.hotmail.com, you need to enter your Login and Password. When you provide your Login/Password, the Hotmail application first checks whether you are a valid user of the site or not. If you are a valid user then application checks for the resources on which you have access/permission (your mail messages, address books).  You have permission to view only your mails. This means your authorization/permission is limited to your own data. Had you been an administrator of the site, you may have accessed much more resources than you can do now. Authentication is checking a user as a valid user, Authorization is finding their permission/right on the application resources or the data.

Application Design

Consider you are designing a client-server application and have to decide about the application's security. You can use Windows Authentication and Authorization as the security option for your application. Let us understand this by taking an example. You are designing an application for an enterprise on (Let us call it WAA [Windows Authentication and Authorization ]):

Now, define various NT user groups in your Windows OS for your application which shall represent several authorization levels for your application. Also, add windows users to these newly created groups, whom you wish to give these access:
  
  
		
	

Its time for SQL Server excercise; Create new login(s) in SQL Server security (through SQL Server Enterprise Manager). These logins will be same as windows groups. Give proper access to the newly created login (give access on your Application's database and required objects, tables etc). Now, you can connect from a windows client machine to this database using windows authentication. There is no need to give SQL user id and password.

  
 
  
		
	

Programming World

After setting the SQL Server, let us now dive into programming world. For full code project click here

Make a VB project named as WAA. Now, add one form (frmMain) and one module (modWAA) in the project. Go to project's properties and make method main() as the startup object for the project. This method is declared in the module (modWAA). the main() method, first authenticate the connected windows user by trying to connect to the SQL Server databse using windows authentication. After the user is authenticated as a valid user, then based on user's group, and its authorization level permissions are set on various objects (forms/menus/buttons).

modWAA: Declaration part in modWAA . Some API declaration is added in order to find user's group.


Option Explicit

Public gstrUsrName As String    'Global variable, holds User Id
Public giAccessLevel As Integer 'Global variable, holds User's access level
Dim mstrServer As String        'Module level variable, holds NT/2000 Domain Name
                                '   this is required to get user group
                                '   (You may want to put it in INI file / registry)
Dim mstrGrpAdmin As String      'Module variable, holds name of Administrator Group
                                '   ("WAA_ADMINISTRATOR" in our case)
Dim mstrGrpModule1 As String    'Holds "WAA_USER_MODULE1"
Dim mstrGrpModule2 As String    'Holds "WAA_USER_MODULE2"

'*******************************************************************************************
'Declaration for finding NT Group/User
'*******************************************************************************************
Private Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" _
    (ByVal lpBuffer As String, nSize As Long) As Long

Private Declare Function NetUserGetGroups Lib "netapi32" _
    (lpServer As Any, UserName As Byte, ByVal Level As Long, _
    lpBuffer As Long, ByVal PrefMaxLen As Long, lpEntriesRead As _
    Long, lpTotalEntries As Long) As Long

Private Declare Function NetUserGetLocalGroups _
    Lib "Netapi32.dll" (lpServer As Any, UserName As Byte, _
     ByVal Level As Long, ByVal Flags As Long, lpBuffer As Long, _
     ByVal MaxLen As Long, lpEntriesRead As Long, _
     lpTotalEntries As Long) As Long


Private Declare Sub CopyMemory Lib "kernel32" _
    Alias "RtlMoveMemory" (Destination As Any, Source As Any, _
        ByVal Length As Long)

Private Declare Function lstrlenW Lib "kernel32" _
        (ByVal lpString As Long) As Long

Private Declare Function NetApiBufferFree Lib "netapi32" _
        (ByVal pBuffer As Long) As Long

'Declare Windows API Constants
Const HWND_BROADCAST = &HFFFF
Const WM_WININICHANGE = &H1A
'*******************************************************************************************

modWAA:  main() method, which is entry point for the project and is used for authentication and authorization


'*************************************************************************
'This main procedure is the starting point (Entry Point) for the project.
'   Here, we first Authenticate the user
'   and if user is a valid user, then based on user's athority/access,
'   we will enable/disable several option in the Application (Authorization)
'*************************************************************************
Sub Main()
    
    On Error GoTo ErrHandler
    
    Dim cnConnection As ADODB.Connection        'Connection object
    Dim strCon As String                        'Holds Connection String
    
    '*************************************************************************
    'Prepare Connection string
    '*************************************************************************
    strCon = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;" & _
                "Initial Catalog=DATABASENAME; Data Source=SQLSERVERNAME"
    
    '*************************************************************************
    'Try to connect to SQL server using ADODB. Since we do not provide SQL
    '   Server User Id and Password, it will automatically take it as windows
    '   user id and password (Windows Authentication).
    '*************************************************************************
    Set cnConnection = New ADODB.Connection
    
    cnConnection.Open strCon

    
    '*************************************************************************
    'If user gets connected, user is a valid user (else flash a message in Error
    '   Handler). Get the User Name from NT for Authorization
    '
    'There are sevral methods to find out the connected user and its group.
    '   Since we are using Windows NT/2000. We can use the show simplest way.
    '*************************************************************************
    gstrUsrName = Environ("USERNAME")
    
    '*************************************************************************
    'Now, find the user group
    '   A user can be part of more than one group
    '   (ie; WAA_MODULE1 and WAA_ADMINISTRATOR)
    '
    'Write logic/code to find the highest group
    '   (in our case it is "WAA_ADMINISTRATOR").
    '   Set this acces level in a global variable.
    '
    'Based on this group, find the highest acces level (Authorization)
    '   0:  WAA_ADMINISTRATOR
    '   1:  WAA_USER_MODULE1
    '   2:  WAA_USER_MODULE2
    '  -1:  This user is not added in any group
    '*************************************************************************
    giAccessLevel = GetAccessLevel()
    
    'If not a part of any group
    If giAccessLevel = -1 Then
        MsgBox "User " & UCase(gstrUsrName) & _
            " does not have permissions to run the application.", vbInformation
        GoTo ExitSub
    End If
    
    'Show the First/Master form of the application
    '   (Set various options on this based on access level)
    frmMain.Show
    
    GoTo ExitSub

ErrHandler:
    
    'Show error, if User is not a valid windows user for application.
    If Err.Number = -2147217843 Then
        MsgBox "Logon failed for User " & UCase(gstrUsrName) & ". " & vbCrLf & _
            "Please Contact System Administrator.", vbInformation
    Else
        MsgBox Err.Description, vbInformation
    End If
    
    GoTo ExitSub

ExitSub:
    
    'Close the connection
    If cnConnection.State = adStateOpen Then
        cnConnection.Close
    End If
    
    'Release connection
    Set cnConnection = Nothing
    
    Exit Sub
    
End Sub

modWAA: other methods, used to find the user group and its access level.



'*************************************************************************
'This method will get a list of all Groups in which the connected user
'   is attached.
'Now, based on the application authorization structure designed by you
'   put logic in this function which will get you the highest access level
'   for the connected user.
'Based on this access level, programmer will enable/disable the forms/menus
'   /buttons for this user (which is authorization for the application).
'
'RETURNS:
'   0:  WAA_ADMINISTRATOR
'   1:  WAA_USER_MODULE1
'   2:  WAA_USER_MODULE2
'  -1:  This user is not added in any group
'*************************************************************************
Private Function GetAccessLevel() As Integer
    
    'Get the MAX NT user groups
    Dim astrNTGrp() As String       'String array, holds list of all NT Groups
                                    '   for the connected user
    Dim iCount As Integer           'Array counter
    Dim iTotal As Integer           'Ubound of the group array
    Dim blnGrpAdmin As Boolean      'Is Admin group (WAA_ADMINISTRATOR)
    Dim blnGrpModule1 As Boolean    'Is Module1 group (WAA_USER_MODULE1)
    Dim blnGrpModule2 As Boolean    'Is Module2 group (WAA_USER_MODULE2)
    
    '*************************************************************************
    'Initialize required variables.
    '*************************************************************************
    
    'mstrServer is the Domain Name on which your application
    '   is to be deployed. Set this to blank (""), if you are not running the
    '   application on any NT/2000 network domain
    '   (you are running it on a standalone PC)
    mstrServer = "WAA_DOMAIN"
    
    mstrGrpAdmin = "WAA_ADMINISTRATOR"
    mstrGrpModule1 = "WAA_USER_MODULE1"
    mstrGrpModule2 = "WAA_USER_MODULE2"
    '*************************************************************************
    
    '*************************************************************************
    'IMP:   Set the third parameter (bLocalGroups)  as true, if you are not
    '       running the application on any NT/2000 network domain
    '       (you are running it on a standalone PC)
    '*************************************************************************
    
    'Get the list of all the user
    astrNTGrp = GetUserGroups(mstrServer, gstrUsrName, False)
    
    'Get Ubound of the array
    iTotal = UBound(astrNTGrp)
    
    '*************************************************************************
    'Put logic to set highest group (access level)
    '*************************************************************************
    For iCount = 0 To iTotal
        If astrNTGrp(iCount) = mstrGrpAdmin Then
            blnGrpAdmin = True
        ElseIf astrNTGrp(iCount) = mstrGrpModule1 Then
            blnGrpModule1 = True
        ElseIf astrNTGrp(iCount) = mstrGrpModule2 Then
            blnGrpModule2 = True
        End If
    Next
    
    'Check the Highest group combinations
    If blnGrpAdmin Then
        GetAccessLevel = 0
    ElseIf blnGrpModule1 And Not blnGrpAdmin Then
        GetAccessLevel = 1
    ElseIf blnGrpModule2 And Not blnGrpAdmin Then
        GetAccessLevel = 2
    ElseIf (Not blnGrpModule1) And (Not blnGrpModule1) And (Not blnGrpAdmin) Then
        GetAccessLevel = -1
    End If
    '*************************************************************************
    
End Function

'******************************************************
'DESCRIPTION:   RETURNS A STRING ARRAY OF NT GROUPS A GIVEN
'               USER BELONGS TO ON A GIVEN SERVER

'PARAMETERS:

'ServerName:    Name of Server
'UserName:      Name of User
'bLocalGroups  (Optional): if True returns local groups. Defaults
'               to false, meaning that global groups are returned

'RETURNS:       If Successful, a string array of each NT group
'               UserName belongs to on ServerName.  Otherwise,
'               returns a 1 element array with "" as the only
'               element

'REQUIRES:
'                -- Windows NT4 or Windows 2000

'                -- VB 6 because string array is returned
'                   (though it would be easy to modify for VB5)
'               --  Assumes Option Base 1 is not set

'EXAMPLE:       dim sUserArray() as String, iCtr as integer
'               sUserArray = GetUserGroups("MyServer", "JoeUser")
'               If sUserArray(0) <> "" then
'                 For iCtr = 0 to Ubound(sUserArray)
'                   debug.print sUserArray(ictr)
'                 Next
'               End if
'***********************************************************
Public Function GetUserGroups(ByVal ServerName As String, ByVal UserName As String, _
    Optional bLocalGroups As Boolean = False) As String()

    Dim bytUser() As Byte
    Dim bytServer() As Byte
    
    Dim lBuffer As Long
    Dim lEntries As Long
    Dim lMaxLen As Long
    Dim lTotalEntries As Long
    Dim lRet As Long
    Dim lGroups() As Long
    Dim sGroups() As String
    Dim bytBuffer() As Byte
    Dim iCtr As Integer
    Dim lLen As Long
    
    If bLocalGroups Then
        ServerName = vbNullChar
    Else
        If Left(ServerName, 2) <> "\\" Then ServerName = _
       "\\" & ServerName
    
    End If
    
    bytServer = ServerName & vbNullChar
    bytUser = UserName & vbNullChar
    
    If bLocalGroups Then
     lRet = NetUserGetLocalGroups(bytServer(0), bytUser(0), 0, 0, _
       lBuffer, 1024, lMaxLen, lTotalEntries)
    
    Else
     lRet = NetUserGetGroups(bytServer(0), bytUser(0), 0, _
       lBuffer, 1024, lMaxLen, lTotalEntries)
    
    End If
        
    If lRet = 0 And lMaxLen > 0 Then
          ReDim lGroups(lMaxLen - 1) As Long
          ReDim sGroups(lMaxLen - 1) As String
          CopyMemory lGroups(0), ByVal lBuffer, lMaxLen * 4
          For iCtr = 0 To lMaxLen - 1
              lLen = lstrlenW(lGroups(iCtr)) * 2
               If lLen > 0 Then
                    ReDim bytBuffer(lLen - 1) As Byte
                    CopyMemory bytBuffer(0), ByVal lGroups(iCtr), _
                       lLen
                    sGroups(iCtr) = bytBuffer
                    
                End If
          Next
    Else
            ReDim sGroups(0) As String
    End If
     
    If lBuffer > 0 Then NetApiBufferFree (lBuffer)
    GetUserGroups = sGroups

End Function


frmMain: in form load, put code to disable/enable several application objects based on authorization. Various Access levels:


Option Explicit

Private Sub cmdModule1_Click()
    MsgBox "This is First Module." & vbCrLf & _
        "Only users under WAA_ADMINISTRATOR and WAA_USER_MODULE1 are allowed" & _
        " to use this module.", vbExclamation
End Sub

Private Sub cmdModule2_Click()
    MsgBox "This is Second Module." & vbCrLf & _
        "Only users under WAA_ADMINISTRATOR and WAA_USER_MODULE2 are allowed" & _
        " to use this module.", vbExclamation
End Sub

Private Sub Form_Load()
    
    'Enable authorised module(s) to the connected user
    If giAccessLevel = 0 Then
        cmdModule1.Enabled = True
        cmdModule2.Enabled = True
    ElseIf giAccessLevel = 1 Then
        cmdModule1.Enabled = True
        cmdModule2.Enabled = False
    ElseIf giAccessLevel = 2 Then
        cmdModule1.Enabled = False
        cmdModule2.Enabled = True
    End If

    
    'Follows the Rest of your application
    '........
    '........
    '........
    
End Sub

Summary

In this article, we tried some code to understand and implement Windows Authentication and Authorization in our real life application design.

I hope you must have found this article useful. You are more than welcome to send your feedback.

About the author

Name :

Pawan Kumar

Email :

pawan@dotnetspy.com

Profile :

Pawan Kumar - one of the founder of DotNetSpy - is a Microsoft MCSD, Software Developer and Author.

Back to DotNetSpy Home