Don Kiely's Technical Blatherings

All Things Technical in .NET, SQL Server, and Security

<September 2008>
SuMoTuWeThFrSa
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011


Navigation

Personal

Subscriptions

News

Post Categories



Don't Reinvent the Wheel: Use CredUIPromptForCredentials as a Login Window

A common requirement for Windows desktop apps is to make the user log into the app, particularly if the app can't or doesn't use Windows integrated authentication. This is so common a requirement that Windows has what you need built in: CredUIPromptForCredentials. I recently used this in a project and so had to do all the research to make it work. This application happened to be written in VB.NET, but the ideas are similar for C#. If you're using C++, you'll need the Include and Lib directories from the Platform SDK.

Here's the code I used to prompt the user for login credentials:

Dim bUseCredUI As Boolean = WindowsVersionForCredentials()
Dim frmLogin As Login
Dim sUser As String
Dim sPwd As String
Dim bSave As Boolean
Dim bAttemptAuth As Boolean

Do While True
     If bUseCredUI Then
         
bAttemptAuth = CredUILogin(sUser, sPwd, bSave)
     Else
         
'Use a custom login form
     End If

     If bAttemptAuth Then
          'Do whatever is necessary to authenticate user
    
Else
          DoLogout()
          Exit Do
    
End If
Loop

The code starts with a call to a custom WindowsVersionForCredentials function to determine whether the code is running on a version of Windows that supports CredUIPromptForCredentials, namely Windows XP or Windows Server 2003. (More about WindowsVersionForCredentials below.) If CredUIPromptForCredentials is available, the code calls the custom CredUILogin function, which calls CredUIPromptForCredentials and returns a Boolean indicating whether the user entered any credentials. Here I'm passing reference variables to get the user name, password, and whether the user checked the "Remember my password" box.

Here is the login box, which should look familiar to any Windows users. Note that it includes a custom prompt.

CredUIPromptForCredentials login box

The rest of the code above uses a custom login form if the code is running on Windows 2000. Then, if in either case the user entered credentials, the code does whatever is necessary to authenticate the user. This is commonly a database lookup.

CredUILogin Function

Here is the crux code, the call to CredUIPromptForCredentials:

Private Function CredUILogin(ByRef sUserName As String, ByRef sPassword As String, ByRef bSave As Boolean) As Boolean

     Dim info As New CredentialManager.CREDUI_INFO
     info.hwndParent = Me.Handle
     info.pszCaptionText = Application.ProductName
     info.pszMessageText = "Please enter " & Application.ProductName & " login information"

     Dim result As CredUIReturnCodes

     Dim flags As CREDUI_FLAGS
     flags = CREDUI_FLAGS.GENERIC_CREDENTIALS Or _
          CREDUI_FLAGS.SHOW_SAVE_CHECK_BOX Or _
          CREDUI_FLAGS.ALWAYS_SHOW_UI Or _
          CREDUI_FLAGS.EXPECT_CONFIRMATION

     Dim sUser As String
     Dim sPwd As String

     result = CredUI.PromptForCredentials(info, _
          Application.ProductName, 0, _
          sUser, sPwd, _
          bSave, flags)

     If result = CredUIReturnCodes.NO_ERROR Then
         
sUserName = sUser
          sPassword = sPwd

          Return True
     Else
          Return False
     End If

End Function

CredUIPromptForCredentials has a boatload of parameters, but here I'm using a bare minimum. The trickiest part is setting up the CREDUI_INFO structure with the parent form's window handle, the caption text for the title bar, and the text for the prompt. The code sets some flags, here to

  • Use generic credentials rather than a Windows login
  • Show the Save my password box
  • Show the login even if the credentials are cached (used only with generic credentials)
  • Say whether the code will confirm the credentials after making use of them

Check the documentation for the several other flags you can use.

Finally the code calls the CredUIPromptForCredentials function--actually the CredUI.PromptForCredentials method--passing in the info structure; the application name used to store the credentials if the user opts to do that (any string will do, as long as it is unique for this set of credentials); 0 for a reserved parameter; reference variables for user name, password and the save flag; and the flags you set.

The CredUILogin function then returns true to indicate that the user entered credentials or false if not.

Astute readers will no doubt notice that there is just not that much support for calling Win32 APIs as is implied in this code like, oh, say a p/invoke statement. Yes, of course! To do all the heavy lifting I used a CredentialsManager component, CredUI, written by Duncan Mackenzie and published on MSDN.

Confirming Credentials

The last step in using the CredUIPromptForCredentials function is to optionally confirm the credentials. This uses another API function, CredUIConfirmCredentials, which must be called after you validate the credentials, if you passed the CREDUI_FLAGS.GENERIC_CREDENTIALS flag, and if the prompt function returned NO_ERROR. Duncan's CredentialsManager makes this easy.

If the credentials are good:

'Credentials are good, so let Windows persist them
CredUI.ConfirmCredentials(Application.ProductName, True)

Or if they are bad:

'Credentials failed, so don't let Windows persist them
CredUI.ConfirmCredentials(Application.ProductName, False)

Here again I'm passing the Application.ProductName as the unique string with which these credentials will be associated. Once this function is called with True for the second parameter, the credentials are saved for this user.

WindowsVersionForCredentials function

The WindowsVersionForCredentials function simply uses the OperatingSystem object to make sure that the code is running on Windows XP or Windows Server 2003. This might not be as robust as you need, because this particular application could only be installed on Windows 2000 or later, so I didn't need to worry about Windows 9x at all. But the docs 'splain things pretty thoroughly. Way better than the old tricks we had to use with Win32 API programming to find out what the app was running on!

Public Function WindowsVersionForCredentials() As Boolean
    
'Determine if the app is running on a version of Windows that can use the CredUI API in Windows,
    
'available for WinXP and later. IOW, not Windows 2000.

     Dim bOkay As Boolean = False
    
Dim OS As OperatingSystem = Environment.OSVersion

     If OS.Platform = PlatformID.Win32NT Then
         
If OS.Version.Major >= 5 Then
              
If OS.Version.Minor > 0 Then
                   
'Version 5.0 is Win2K, 5.1 is WinXP
                   
bOkay = True
              
End If
         
End If
    
End If

     Return bOkay
End Function

Enjoy!


Update, 6 June 2005: Valery, who writes an excellent security blog, talks about the problem of clearing the credential cache, and includes a nifty utility to make it easier. Give it a try!

posted on Tuesday, January 04, 2005 2:25 PM by donkiely


# Finetre di login in Windows @ Wednesday, January 05, 2005 4:21 PM

donkiely

# [Secure Development] Using the Credential Management API to store user passwords @ Wednesday, April 27, 2005 7:04 AM

[Secure Development] Using the Credential Management API to store user passwords

donkiely




Powered by Dot Net Junkies, by Telligent Systems