Export (0) Print
Expand All

Using Credential Management in Windows XP and Windows Server 2003

 

Duncan Mackenzie
Microsoft Developer Network

January 2003

Summary: Shows how to retrieve user credentials using the DPAPI function, CredUIPromptForCredentials, in Microsoft Windows XP and Windows Server 2003 to obtain authentication information in a secure and standard way. (16 printed pages)

Applies to:
   Microsoft® .NET
   Microsoft Windows XP
   Microsoft Windows Server 2003

Download the source code for this article.

Download the credentialsdemo.exe sample file. (58.5 KB)

Contents

Introduction
Stored User Names and Passwords
Creating the Credential API Class in .NET
Requesting User Credentials
Using Your Own Graphic
Summary

Introduction

Sometimes your application requires user-supplied credentials to access a protected resource, such as a database or a FTP site. Obtaining and storing a user's ID and password, however, introduces a security risk into your system. Where possible, you shouldn't have the user supply credentials at all (by using integrated authentication for your database, for example), but sometimes it cannot be avoided. If you do need to request credentials from the user, and your application will be running on Microsoft® Windows® XP or Microsoft® Windows Server 2003, then the operating system provides functions to make this task easier.

Stored User Names and Passwords

Windows XP and Windows Server 2003 use a feature called "Stored User Names and Passwords" (see Figure 1) to associate a set of credentials with a single Windows user account, storing those credentials using the Data Protection API (DPAPI).

Click here for larger image.

Figure 1. Credential Management dialog boxes in Windows XP

Your application, if it is running on Windows XP or Windows .NET, can use the Credential Management API functions to prompt the user for credentials. Using these APIs will provide you with a consistent user interface (see Figure 2) and will allow you to automatically support the caching of these credentials by the operating system.

Ff648659.dpapiusercredentials_fig2(en-us,PandP.10).gif

Figure 2. Standard Windows XP credential dialog box

The issues involved in requesting, storing, and using a user's credentials in your application are discussed in much greater detail in Writing Secure Code by Michael Howard and David LeBlanc. I suggest you read that book for more information. In this article, I'll just be showing you how to use the Credential Management APIs from your Microsoft® Visual Basic® .NET and C# applications.

Creating the Credential API Class in .NET

Declaring the API Functions

As these Credential Management functions are Win32 API calls, you will need to create extern (C#) or Declare (Visual Basic .NET) definitions to be able to access them. In addition to the functions themselves, there are also constants and structures required to access them. The constants are organized into pre-defined sets, so I have chosen to implement those sets as enumerations in my .NET code, to make the API calls easier to use.

Private Declare Unicode _
    Function CredUIPromptForCredentials _
        Lib "credui" Alias "CredUIPromptForCredentialsW" _
            (ByRef creditUR As CREDUI_INFO, _
             ByVal targetName As String, _
             ByVal reserved1 As IntPtr, _
             ByVal iError As Integer, _
             ByVal userName As StringBuilder, _
             ByVal maxUserName As Integer, _
             ByVal password As StringBuilder, _
             ByVal maxPassword As Integer, _
             ByRef iSave As Integer, _
             ByVal flags As CREDUI_FLAGS) _
    As CredUIReturnCodes

Private Declare Unicode _
    Function CredUIParseUserName _
        Lib "credui" Alias "CredUIParseUserNameW" _
            (ByVal userName As String, _
             ByVal user As StringBuilder, _
             ByVal userMaxChars As Integer, _
             ByVal domain As StringBuilder, _
             ByVal domainMaxChars As Integer) _
    As CredUIReturnCodes

Private Declare Unicode _
    Function CredUIConfirmCredentials _
        Lib "credui" Alias "CredUIConfirmCredentialsW" _
            (ByVal targetName As String, _
             ByVal confirm As Boolean) _
    As CredUIReturnCodes

Public Declare Auto _
    Function DeleteObject Lib "Gdi32" _
        (ByVal hObject As IntPtr) As Boolean


Note   I included the DeleteObject API call, from the GDI32 library, because you will need to use it if you decide to pass your own bitmap into the CredUIPromptForCredentials API. You can see how this API is used later on in this article when I demonstrate the use of a custom bitmap.

Constant and Structure Declarations

For many Win32 API calls, you will need a set of supporting constants (which in this case I chose to represent as enumerations) and possibly one or two Structure declarations. The Credential APIs are no exception to these general rules and require a variety of constants and one structure. In my .NET class, I added an enumeration for the flag parameter of CredUIPromptForCredentials, another enumeration for the possible set of return codes from all three of the Credential API calls, and a declaration of the CREDUI_INFO structure.

<Flags()> Public Enum CREDUI_FLAGS
    INCORRECT_PASSWORD = &H1
    DO_NOT_PERSIST = &H2
    REQUEST_ADMINISTRATOR = &H4
    EXCLUDE_CERTIFICATES = &H8
    REQUIRE_CERTIFICATE = &H10
    SHOW_SAVE_CHECK_BOX = &H40
    ALWAYS_SHOW_UI = &H80
    REQUIRE_SMARTCARD = &H100
    PASSWORD_ONLY_OK = &H200
    VALIDATE_USERNAME = &H400
    COMPLETE_USERNAME = &H800
    PERSIST = &H1000
    SERVER_CREDENTIAL = &H4000
    EXPECT_CONFIRMATION = &H20000
    GENERIC_CREDENTIALS = &H40000
    USERNAME_TARGET_CREDENTIALS = &H80000
    KEEP_USERNAME = &H100000
End Enum

Public Enum CredUIReturnCodes As Integer
    NO_ERROR = 0
    ERROR_CANCELLED = 1223
    ERROR_NO_SUCH_LOGON_SESSION = 1312
    ERROR_NOT_FOUND = 1168
    ERROR_INVALID_ACCOUNT_NAME = 1315
    ERROR_INSUFFICIENT_BUFFER = 122
    ERROR_INVALID_PARAMETER = 87
    ERROR_INVALID_FLAGS = 1004
End Enum

Public Structure CREDUI_INFO
    Public cbSize As Integer
    Public hwndParent As IntPtr
    <MarshalAs(UnmanagedType.LPWStr)> Public pszMessageText As String
    <MarshalAs(UnmanagedType.LPWStr)> Public pszCaptionText As String
    Public hbmBanner As IntPtr
End Structure


Create Wrapper Functions Around API Calls

This step is not required. You could simply make your API declares Public (instead of Private, as in my code) and call them directly from your applications. I find that calling APIs often involves a bit of work, however, and I prefer to shield the eventual user of my code from those details by wrapping the API calls.

Private Const MAX_USER_NAME As Integer = 100
Private Const MAX_PASSWORD As Integer = 100
Private Const MAX_DOMAIN As Integer = 100

Public Shared Function PromptForCredentials( _
         ByRef creditUI As CREDUI_INFO, _
         ByVal targetName As String, _
         ByVal netError As Integer, _
         ByRef userName As String, _
         ByRef password As String, _
         ByRef save As Boolean, _
         ByVal flags As CREDUI_FLAGS) _
    As CredUIReturnCodes

    Dim saveCredentials As Integer
    Dim user As New StringBuilder(MAX_USER_NAME)
    Dim pwd As New StringBuilder(MAX_PASSWORD)
    saveCredentials = Convert.ToInt32(save)
    creditUI.cbSize = Marshal.SizeOf(creditUI)
    Dim result As CredUIReturnCodes
    result = CredUIPromptForCredentials( _
                    creditUI, targetName, _
                    IntPtr.Zero, netError, _
                    user, MAX_USER_NAME, _
                    pwd, MAX_PASSWORD, _
                    saveCredentials, flags)
    save = Convert.ToBoolean(saveCredentials)
    userName = user.ToString
    password = pwd.ToString
    Return result
End Function

Public Shared Function ParseUserName(ByVal userName As String, _
         ByRef userPart As String, _
         ByRef domainPart As String) _
    As CredUIReturnCodes

    Dim user As New StringBuilder(MAX_USER_NAME)
    Dim domain As New StringBuilder(MAX_DOMAIN)
    Dim result As CredUIReturnCodes
    result = CredUIParseUserName(userName, _
        user, MAX_USER_NAME, _
        domain, MAX_DOMAIN)
    userPart = user.ToString()
    domainPart = domain.ToString()
    Return result
End Function

Public Shared Function ConfirmCredentials(ByVal target As String, _
         ByVal confirm As Boolean) As CredUIReturnCodes
    Return CredUIConfirmCredentials(target, confirm)
End Function


Note   I have made all of my functions Shared/Static for ease of use. Since they don't store any information as class-level properties or variables, there really isn't any reason to force the developer to create an instance of the class before calling them.

With the APIs declared and wrapped, I would suggest placing this code into its own assembly by creating a Class Library project in Microsoft® Visual Studio® .NET (as I have done in the samples that accompany this article) and compiling this code as its only class. By creating an assembly out of this code, you can then reference it from any project that needs it—but you could also just include this class into your project if desired.

Requesting User Credentials

With complete declarations of the API calls, the associated enumerators, and the structure, you are now ready to start using the Credential API from your own application. To demonstrate the various ways in which you can call this API, I have created a simple sample application that connects to a local Microsoft® SQL Server database using SQL authentication. Normally, I would always try to use integrated authentication against my SQL Server, but for the purposes of this sample, I will pretend that I am using a database server that doesn't support that type of authentication. When you call CredUIPromptForCredentials, there are a variety of flags you can set. Although all of these flags are documented on the reference page for this API, I will detail the select few that I use in my application.

  • ALWAYS_SHOW_UI tells the API call to pop up the credentials dialog, even if you have already entered and saved some credentials in the past. Without this flag, and with persisted credentials, the user won't see any prompt at all on subsequent connections through your application. This is very useful if you expect the user to possibly use different credential information for their logon.
  • EXPECT_CONFIRMATION is used in conjunction with the CredUIConfirmCredentials API call (also included in the code earlier in this article). To avoid persisting "bad" credentials, this flag allows for a two-stage process. First you get the credentials from the user, and then try to connect using them, confirming the credentials (and persisting them) only if the connection succeeds.
  • GENERIC_CREDENTIALS specifies that you are only looking for a user ID/password combination, as opposed to domain credentials. I only use this API for this purpose, as secured resources that require domain credentials should be normally handled by the operating system, so I always pass this flag.
  • KEEP_USERNAME modifies the UI of the credential dialog so that only a password can be entered. There are some situations, such as connections to Microsoft® Access databases with the Database Password set, where the User ID is fixed (or doesn't exist in the case of the Access Database Password) and this flag helps to represent that in the credentials UI.
  • SHOW_SAVE_CHECK_BOX ensures that the credentials dialog box includes a check box to provide the user with control over storing these credentials. The user's choice will be returned in the save (boolean) parameter of CredUI.PromptForCredentials.

Here is a sample, using the Credential APIs and the flags described above, to request a password before connecting to a SQL database. First, I create and populate the CREDUI_INFO structure with my target, parent window, and a caption string.

Dim host As String = "MyServer"
Dim info As New CREDUI_INFO()
With info
    .hwndParent = Me.Handle
    .pszCaptionText = host
    .pszMessageText = _
        String.Format("Please Enter Credentials for {0}", host)
End With


Next, I specify the flags for this call to CredUIPromptForCredentials, which in this case tell the API that I will be requesting generic (as opposed to domain) credentials, that I want the UI to include the Save check box, that the dialog box should appear even if the user has previously entered and saved a set of credentials, and that before any credentials are persisted, I will provide confirmation through the CredUIConfirmCredentials API.

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

Once I have my flags and CREDUI_INFO structure, I then call the PromptForCredentials API call, passing in all of my information.

Dim result As CredUIReturnCodes
result = CredUI.PromptForCredentials(info, _
    host, 0, _
    userid, password, savePwd, flags)

If I hadn't specified CREDUI_FLAGS.ALWAYS_SHOW_UI, then the credential dialog would only appear if there were not any stored credentials available for this particular target. Generally, this means that (without this flag) the dialog appears only the first time you call this API, which provides a good user experience. Regardless of whether or not the dialog appears, this API call will return a result code indicating success or failure, which you should check before proceeding to use the returned user ID and password values.

In my code, I check for a value of NO_ERROR (which could more positively be described as SUCCESS) before attempting my database connection. If the API call returns NO_ERROR but my database connection fails, then I use the ConfirmCredentials call to tell the Credential system that these credentials are not valid and therefore should not be persisted. If the connection to the database succeeds, I use the ConfirmCredentials call to inform the Credential system that this user ID and password combination is valid and that it should be persisted.

Dim connString As String
Dim password, userid As String
Dim selectAuthors As String = _
    "Select au_id, au_lname, au_fname From authors"

If result = CredUIReturnCodes.NO_ERROR Then
    connString = String.Format( _
    "Password={1};User ID={0};" & _
    "Initial Catalog=pubs;" & _
    "Data Source=MyServer", _
        userid, password)
    Dim conn As New SqlConnection(connString)
    Try
        conn.Open()
        CredUI.ConfirmCredentials(host, True)
    Catch sqlEx As SqlException
        If sqlEx.Number = 18456 Then
            MsgBox("Authentication Failed")
            CredUI.ConfirmCredentials(host, False)
        End If
    Catch ex As Exception
        MsgBox("Connection Error")
        CredUI.ConfirmCredentials(host, False)
    End Try

    If conn.State = ConnectionState.Open Then
        Dim cmdAuthors As New SqlCommand( _
            selectAuthors, _
            conn)
        Dim daAuthors As New SqlDataAdapter(cmdAuthors)
        Dim dtAuthors As New DataTable("Authors")
        daAuthors.Fill(dtAuthors)
        retrievedData.SetDataBinding(dtAuthors.DefaultView, "")
    End If
ElseIf result <> CredUIReturnCodes.ERROR_CANCELLED Then
    MsgBox("There was an error in authentication")
End If


Using Your Own Graphic

The credential dialog looks pretty good already (see Figure 2)—very nice and consistent—but I can understand if you want to personalize it a bit. Lucky for you, the PromptForCredentials API allows you to provide your own 320x60 graphic (see Figure 3) to be used in place of the default image.

Ff648659.dpapiusercredentials_fig3(en-us,PandP.10).gif

Figure 3. A custom graphic added to the standard credential dialog box.

Doing this in your own code is pretty easy because the System.Drawing.Bitmap class provides a handy method, GetHbitmap, to obtain a native handle to the underlying graphic. You need to create an instance of System.Drawing.Bitmap, and then set the .hbmBanner member of your CREDUI_INFO structure to the native handle of that bitmap. Once you are done with this CREDUI_INFO structure (you have made your call to PromptForCredentials), you need to release this native handle to avoid any form of memory leak. To release the handle, you need to use another API call, DeleteObject, which I have included in my CredUI class to make using a custom bitmap as easy as possible.

Dim credBmp As New Bitmap("d:\credui.bmp")

Dim info As New CREDUI_INFO()
With info
    .hwndParent = Me.Handle
    .pszCaptionText = host
    .pszMessageText = _
        String.Format("Please Enter Credentials for {0}", host)
    .hbmBanner = credBmp.GetHbitmap()
End With

'make call to PromptForCredentials
'...
CredUI.DeleteObject(info.hbmBanner)


To provide consistency with Windows, I would suggest sticking with the default graphic, but it is good to have the ability to customize the dialog if you want to. I load the image from a file in the example code, but you could also just draw it on the fly using GDI+ if you were so inclined.

Summary

If you need to prompt the user for database, Web site, or any other set of credentials, using the built-in operating system features is the best way to accomplish this. In addition to providing the user with a consistent user interface, you also gain the benefits of the operating system's cache of credentials tied to the logged on user. If you want more information on security issues in development, check out Writing Secure Code by Michael Howard and David LeBlanc.

For more information on topics discussed in this article:

   

For background on the author, see Duncan Mackenzie's profile on GotDotNet.
Show:
© 2014 Microsoft