Using the Credential Management API

Wrapper Code

Following secure development best practice, an application should obtain and store user credentials securely. Microsoft has provided a means to accomplish this on the desktop: the unmanaged Credential Management API, which exposes the capability to provide a standard login experience for the user which is also secure. There are a number of benefits to utilising this:

  • The code is maintained by Microsoft. I put this in the first because I think it is an important advantage.
  • The user experience is consistent.
  • The user credentials can be shared easily across applications.
  • It's secure. It's well-tested as being secure. If a security flaw is discovered, you can be sure that Microsoft will fix it.

This is documented on MSDN: Credentials Management > Authentication Reference

While researching this, I discovered some excellent resources:

What I didn't find was a comprehensive code sample of a Generic dialog implementation, so I decided to put one together.

Before going any further, I should highlight that the Credential Management API is only available on Windows XP / Windows Server 2003 onwards.

I don't usually post such a large slice of code as this but I felt that it would be the clearest way to describe the class and also has the benefit that readers can cut & paste the entire sample easily. So here is my entire credential wrapper code:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace SecureCredentialsLibrary
{
    /// <summary>Encapsulates dialog functionality from the Credential Management API.</summary>
    public sealed class CredentialsDialog
    {
        /// <summary>The only valid bitmap height (in pixels) of a user-defined banner.</summary>
        private const int ValidBannerHeight = 60;
        /// <summary>The only valid bitmap width (in pixels) of a user-defined banner.</summary>
        private const int ValidBannerWidth = 320;
       
        /// <summary>Initializes a new instance of the <see cref="T:SecureCredentialsLibrary.CredentialsDialog"/> class
        /// with the specified target.</summary>
        /// <param name="target">The name of the target for the credentials, typically a server name.</param>
        public CredentialsDialog(string target) : this(target, null)
        { }
        /// <summary>Initializes a new instance of the <see cref="T:SecureCredentialsLibrary.CredentialsDialog"/> class
        /// with the specified target and caption.</summary>
        /// <param name="target">The name of the target for the credentials, typically a server name.</param>
        /// <param name="caption">The caption of the dialog (null will cause a system default title to be used).</param>
        public CredentialsDialog(string target, string caption) : this(target, caption, null)
        { }
        /// <summary>Initializes a new instance of the <see cref="T:SecureCredentialsLibrary.CredentialsDialog"/> class
        /// with the specified target, caption and message.</summary>
        /// <param name="target">The name of the target for the credentials, typically a server name.</param>
        /// <param name="caption">The caption of the dialog (null will cause a system default title to be used).</param>
        /// <param name="message">The message of the dialog (null will cause a system default message to be used).</param>
        public CredentialsDialog(string target, string caption, string message) : this(target, caption, message, null)
        { }
        /// <summary>Initializes a new instance of the <see cref="T:SecureCredentialsLibrary.CredentialsDialog"/> class
        /// with the specified target, caption, message and banner.</summary>
        /// <param name="target">The name of the target for the credentials, typically a server name.</param>
        /// <param name="caption">The caption of the dialog (null will cause a system default title to be used).</param>
        /// <param name="message">The message of the dialog (null will cause a system default message to be used).</param>
        /// <param name="banner">The image to display on the dialog (null will cause a system default image to be used).</param>
        public CredentialsDialog(string target, string caption, string message, Image banner)
        {
            this.Target = target;
            this.Caption = caption;
            this.Message = message;
            this.Banner = banner;
        }
       
        private bool _alwaysDisplay = false;
        /// <summary>
        /// Gets or sets if the dialog will be shown even if the credentials
        /// can be returned from an existing credential in the credential manager.
        /// </summary>
        public bool AlwaysDisplay
        {
            get
            {
                return _alwaysDisplay;
            }
            set
            {
                _alwaysDisplay = value;
            }
        }
       
        private bool _excludeCertificates = true;
        /// <summary>Gets or sets if the dialog is populated with name/password only.</summary>
        public bool ExcludeCertificates
        {
            get
            {
                return _excludeCertificates;
            }
            set
            {
                _excludeCertificates = value;
            }
        }
       
        private bool _persist = true;
        /// <summary>Gets or sets if the credentials are to be persisted in the credential manager.</summary>
        public bool Persist
        {
            get
            {
                return _persist;
            }
            set
            {
                _persist = value;
            }
        }
       
        private bool _keepName = false;
        /// <summary>Gets or sets if the name is read-only.</summary>
        public bool KeepName
        {
            get
            {
                return _keepName;
            }
            set
            {
                _keepName = value;
            }
        }
       
        private string _name = String.Empty;
        /// <summary>Gets or sets the name for the credentials.</summary>
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                if (value != null)
                {
                    if (value.Length > CREDUI.MAX_USERNAME_LENGTH)
                    {
                        string message = String.Format(
                            Thread.CurrentThread.CurrentUICulture,
                            "The name has a maximum length of {0} characters.",
                            CREDUI.MAX_USERNAME_LENGTH);
                        throw new ArgumentException(message, "Name");
                    }
                }
                _name = value;
            }
        }
       
        private string _password = String.Empty;
        /// <summary>Gets or sets the password for the credentials.</summary>
        public string Password
        {
            get
            {
                return _password;
            }
            set
            {
                if (value != null)
                {
                    if (value.Length > CREDUI.MAX_PASSWORD_LENGTH)
                    {
                        string message = String.Format(
                            Thread.CurrentThread.CurrentUICulture,
                            "The password has a maximum length of {0} characters.",
                            CREDUI.MAX_PASSWORD_LENGTH);
                        throw new ArgumentException(message, "Password");
                    }
                }
                _password = value;
            }
        }
       
        private bool _saveChecked = false;
        /// <summary>Gets or sets if the save checkbox status.</summary>
        public bool SaveChecked
        {
            get
            {
                return _saveChecked;
            }
            set
            {
                _saveChecked = value;
            }
        }
       
        private bool _saveDisplayed = true;
        /// <summary>Gets or sets if the save checkbox is displayed.</summary>
        /// <remarks>This value only has effect if Persist is true.</remarks>
        public bool SaveDisplayed
        {
            get
            {
                return _saveDisplayed;
            }
            set
            {
                _saveDisplayed = value;
            }
        }
       
        private string _target = String.Empty;
        /// <summary>Gets or sets the name of the target for the credentials, typically a server name.</summary>
        public string Target
        {
            get
            {
                return _target;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentException("The target cannot be a null value.", "Target");
                }
                else if (value.Length > CREDUI.MAX_GENERIC_TARGET_LENGTH)
                {
                    string message = String.Format(
                        Thread.CurrentThread.CurrentUICulture,
                        "The target has a maximum length of {0} characters.",
                        CREDUI.MAX_GENERIC_TARGET_LENGTH);
                    throw new ArgumentException(message, "Target");
                }
                _target = value;
            }
        }
       
        private string _caption = String.Empty;
        /// <summary>Gets or sets the caption of the dialog.</summary>
        /// <remarks>A null value will cause a system default caption to be used.</remarks>
        public string Caption
        {
            get
            {
                return _caption;
            }
            set
            {
                if (value != null)
                {
                    if (value.Length > CREDUI.MAX_CAPTION_LENGTH)
                    {
                        string message = String.Format(
                            Thread.CurrentThread.CurrentUICulture,
                            "The caption has a maximum length of {0} characters.",
                            CREDUI.MAX_CAPTION_LENGTH);
                        throw new ArgumentException(message, "Caption");
                    }
                }
                _caption = value;
            }
        }
       
        private string _message = String.Empty;
        /// <summary>Gets or sets the message of the dialog.</summary>
        /// <remarks>A null value will cause a system default message to be used.</remarks>
        public string Message
        {
            get
            {
                return _message;
            }
            set
            {
                if (value != null)
                {
                    if (value.Length > CREDUI.MAX_MESSAGE_LENGTH)
                    {
                        string message = String.Format(
                            Thread.CurrentThread.CurrentUICulture,
                            "The message has a maximum length of {0} characters.",
                            CREDUI.MAX_MESSAGE_LENGTH);
                        throw new ArgumentException(message, "Message");
                    }
                }
                _message = value;
            }
        }
       
        private Image _banner = null;
        /// <summary>Gets or sets the image to display on the dialog.</summary>
        /// <remarks>A null value will cause a system default image to be used.</remarks>
        public Image Banner
        {
            get
            {
                return _banner;
            }
            set
            {
                if (value != null)
                {
                    if (value.Width != ValidBannerWidth)
                    {
                        throw new ArgumentException("The banner image width must be 320 pixels.", "Banner");
                    }
                    if (value.Height != ValidBannerHeight)
                    {
                        throw new ArgumentException("The banner image height must be 60 pixels.", "Banner");
                    }
                }
                _banner = value;
            }
        }
       
        /// <summary>Shows the credentials dialog.</summary>
        /// <returns>Returns a DialogResult indicating the user action.</returns>
        public DialogResult Show()
        {
            return Show(null, this.Name, this.Password, this.SaveChecked);
        }
       
        /// <summary>Shows the credentials dialog with the specified save checkbox status.</summary>
        /// <param name="saveChecked">True if the save checkbox is checked.</param>
        /// <returns>Returns a DialogResult indicating the user action.</returns>
        public DialogResult Show(bool saveChecked)
        {
            return Show(null, this.Name, this.Password, saveChecked);
        }
       
        /// <summary>Shows the credentials dialog with the specified name.</summary>
        /// <param name="name">The name for the credentials.</param>
        /// <returns>Returns a DialogResult indicating the user action.</returns>
        public DialogResult Show(string name)
        {
            return Show(null, name, this.Password, this.SaveChecked);
        }
       
        /// <summary>Shows the credentials dialog with the specified name and password.</summary>
        /// <param name="name">The name for the credentials.</param>
        /// <param name="password">The password for the credentials.</param>
        /// <returns>Returns a DialogResult indicating the user action.</returns>
        public DialogResult Show(string name, string password)
        {
            return Show(null, name, password, this.SaveChecked);
        }
       
        /// <summary>Shows the credentials dialog with the specified name, password and save checkbox status.</summary>
        /// <param name="name">The name for the credentials.</param>
        /// <param name="password">The password for the credentials.</param>
        /// <param name="saveChecked">True if the save checkbox is checked.</param>
        /// <returns>Returns a DialogResult indicating the user action.</returns>
        public DialogResult Show(string name, string password, bool saveChecked)
        {
            return Show(null, name, password, saveChecked);
        }
       
        /// <summary>Shows the credentials dialog with the specified owner.</summary>
        /// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
        /// <returns>Returns a DialogResult indicating the user action.</returns>
        public DialogResult Show(IWin32Window owner)
        {
            return Show(owner, this.Name, this.Password, this.SaveChecked);
        }
       
        /// <summary>Shows the credentials dialog with the specified owner and save checkbox status.</summary>
        /// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
        /// <param name="saveChecked">True if the save checkbox is checked.</param>
        /// <returns>Returns a DialogResult indicating the user action.</returns>
        public DialogResult Show(IWin32Window owner, bool saveChecked)
        {
            return Show(owner, this.Name, this.Password, saveChecked);
        }
       
        /// <summary>Shows the credentials dialog with the specified owner, name and password.</summary>
        /// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
        /// <param name="name">The name for the credentials.</param>
        /// <param name="password">The password for the credentials.</param>
        /// <returns>Returns a DialogResult indicating the user action.</returns>
        public DialogResult Show(IWin32Window owner, string name, string password)
        {
            return Show(owner, name, password, this.SaveChecked);
        }
       
        /// <summary>Shows the credentials dialog with the specified owner, name, password and save checkbox status.</summary>
        /// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
        /// <param name="name">The name for the credentials.</param>
        /// <param name="password">The password for the credentials.</param>
        /// <param name="saveChecked">True if the save checkbox is checked.</param>
        /// <returns>Returns a DialogResult indicating the user action.</returns>
        public DialogResult Show(IWin32Window owner, string name, string password, bool saveChecked)
        {
            if (Environment.OSVersion.Version.Major < 5)
            {
                throw new ApplicationException("The Credential Management API requires Windows XP / Windows Server 2003 or later.");
            }
            this.Name = name;
            this.Password = password;
            this.SaveChecked = saveChecked;
           
            return ShowDialog(owner);
        }
       
        /// <summary>Confirmation action to be applied.</summary>
        /// <param name="value">True if the credentials should be persisted.</param>
        public void Confirm(bool value)
        {
            switch (CREDUI.ConfirmCredentials(this.Target, value))
            {
                case CREDUI.ReturnCodes.NO_ERROR:
                    break;
               
                case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER:
                    // for some reason, this is encountered when credentials are overwritten
                    break;
               
                default:
                    throw new ApplicationException("Credential confirmation failed.");
                    break;
            }
        }
       
        /// <summary>Returns a DialogResult indicating the user action.</summary>
        /// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
        /// <remarks>
        /// Sets the name, password and SaveChecked accessors to the state of the dialog as it was dismissed by the user.
        /// </remarks>
        private DialogResult ShowDialog(IWin32Window owner)
        {
            // set the api call parameters
            StringBuilder name = new StringBuilder(CREDUI.MAX_USERNAME_LENGTH);
            name.Append(this.Name);
           
            StringBuilder password = new StringBuilder(CREDUI.MAX_PASSWORD_LENGTH);
            password.Append(this.Password);
           
            int saveChecked = Convert.ToInt32(this.SaveChecked);
           
            CREDUI.INFO info = GetInfo(owner);
            CREDUI.FLAGS flags = GetFlags();
           
            // make the api call
            CREDUI.ReturnCodes code = CREDUI.PromptForCredentials(
                ref info,
                this.Target,
                IntPtr.Zero, 0,
                name, CREDUI.MAX_USERNAME_LENGTH,
                password, CREDUI.MAX_PASSWORD_LENGTH,
                ref saveChecked,
                flags
                );
           
            // clean up resources
            if (this.Banner != null) GDI32.DeleteObject(info.hbmBanner);
           
            // set the accessors from the api call parameters
            this.Name = name.ToString();
            this.Password = password.ToString();
            this.SaveChecked = Convert.ToBoolean(saveChecked);
           
            return GetDialogResult(code);
        }
       
        /// <summary>Returns the info structure for dialog display settings.</summary>
        /// <param name="owner">The System.Windows.Forms.IWin32Window the dialog will display in front of.</param>
        private CREDUI.INFO GetInfo(IWin32Window owner)
        {
            CREDUI.INFO info = new CREDUI.INFO();
            if (owner != null) info.hwndParent = owner.Handle;
            info.pszCaptionText = this.Caption;
            info.pszMessageText = this.Message;
            if (this.Banner != null)
            {
                info.hbmBanner = new Bitmap(this.Banner, ValidBannerWidth, ValidBannerHeight).GetHbitmap();
            }
            info.cbSize = Marshal.SizeOf(info);
            return info;
        }
       
        /// <summary>Returns the flags for dialog display options.</summary>
        private CREDUI.FLAGS GetFlags()
        {
            CREDUI.FLAGS flags = CREDUI.FLAGS.GENERIC_CREDENTIALS;
           
            // grrrr... can't seem to get this to work...
            // if (incorrectPassword) flags = flags | CredUI.CREDUI_FLAGS.INCORRECT_PASSWORD;
           
            if (this.AlwaysDisplay) flags = flags | CREDUI.FLAGS.ALWAYS_SHOW_UI;
           
            if (this.ExcludeCertificates) flags = flags | CREDUI.FLAGS.EXCLUDE_CERTIFICATES;
           
            if (this.Persist)
            {
                flags = flags | CREDUI.FLAGS.EXPECT_CONFIRMATION;
                if (this.SaveDisplayed) flags = flags | CREDUI.FLAGS.SHOW_SAVE_CHECK_BOX;
            }
            else
            {
                flags = flags | CREDUI.FLAGS.DO_NOT_PERSIST;
            }
           
            if (this.KeepName) flags = flags | CREDUI.FLAGS.KEEP_USERNAME;
           
            return flags;
        }
       
        /// <summary>Returns a DialogResult from the specified code.</summary>
        /// <param name="code">The credential return code.</param>
        private DialogResult GetDialogResult(CREDUI.ReturnCodes code)
        {
            DialogResult result;
            switch (code)
            {
                case CREDUI.ReturnCodes.NO_ERROR:
                    result = DialogResult.OK;
                    break;
               
                case CREDUI.ReturnCodes.ERROR_CANCELLED:
                    result = DialogResult.Cancel;
                    break;
               
                case CREDUI.ReturnCodes.ERROR_NO_SUCH_LOGON_SESSION:
                    throw new ApplicationException("No such logon session.");
                    break;
               
                case CREDUI.ReturnCodes.ERROR_NOT_FOUND:
                    throw new ApplicationException("Not found.");
                    break;
               
                case CREDUI.ReturnCodes.ERROR_INVALID_ACCOUNT_NAME:
                    throw new ApplicationException("Invalid account name.");
                    break;
               
                case CREDUI.ReturnCodes.ERROR_INSUFFICIENT_BUFFER:
                    throw new ApplicationException("Insufficient buffer.");
                    break;
               
                case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER:
                    throw new ApplicationException("Invalid parameter.");
                    break;
               
                case CREDUI.ReturnCodes.ERROR_INVALID_FLAGS:
                    throw new ApplicationException("Invalid flags.");
                    break;
               
                default:
                    throw new ApplicationException("Unknown credential result encountered.");
                    break;
            }
            return result;
        }
    }
   
    public sealed class GDI32
    {
        private GDI32()
        { }
       
        [DllImport("gdi32.dll", EntryPoint="DeleteObject")]
        public static extern bool DeleteObject(IntPtr hObject);
    }
   
    public sealed class CREDUI
    {
        private CREDUI()
        { }
       
        /// <summary>http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/authentication_constants.asp</summary>
        public const int MAX_MESSAGE_LENGTH        = 100;
        public const int MAX_CAPTION_LENGTH        = 100;
        public const int MAX_GENERIC_TARGET_LENGTH = 100;
        public const int MAX_DOMAIN_TARGET_LENGTH  = 100;
        public const int MAX_USERNAME_LENGTH      = 100;
        public const int MAX_PASSWORD_LENGTH      = 100;
       
        /// <summary>
        /// http://www.pinvoke.net/default.aspx/Enums.CREDUI_FLAGS
        /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/dpapiusercredentials.asp
        /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp
        /// </summary>
        [Flags] public enum FLAGS
        {
            INCORRECT_PASSWORD          = 0x1,
            DO_NOT_PERSIST              = 0x2,
            REQUEST_ADMINISTRATOR      = 0x4,
            EXCLUDE_CERTIFICATES        = 0x8,
            REQUIRE_CERTIFICATE        = 0x10,
            SHOW_SAVE_CHECK_BOX        = 0x40,
            ALWAYS_SHOW_UI              = 0x80,
            REQUIRE_SMARTCARD          = 0x100,
            PASSWORD_ONLY_OK            = 0x200,
            VALIDATE_USERNAME          = 0x400,
            COMPLETE_USERNAME          = 0x800,
            PERSIST                    = 0x1000,
            SERVER_CREDENTIAL          = 0x4000,
            EXPECT_CONFIRMATION        = 0x20000,
            GENERIC_CREDENTIALS        = 0x40000,
            USERNAME_TARGET_CREDENTIALS = 0x80000,
            KEEP_USERNAME              = 0x100000,
        }
       
        /// <summary>http://www.pinvoke.net/default.aspx/Enums.CredUIReturnCodes</summary>
        public enum ReturnCodes
        {
            NO_ERROR                    = 0,
            ERROR_INVALID_PARAMETER    = 87,
            ERROR_INSUFFICIENT_BUFFER  = 122,
            ERROR_INVALID_FLAGS        = 1004,
            ERROR_NOT_FOUND            = 1168,
            ERROR_CANCELLED            = 1223,
            ERROR_NO_SUCH_LOGON_SESSION = 1312,
            ERROR_INVALID_ACCOUNT_NAME  = 1315
        }
       
        /// <summary>
        /// http://www.pinvoke.net/default.aspx/Structures.CREDUI_INFO
        /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/credui_info.asp
        /// </summary>
        public struct INFO
        {
            public int cbSize;
            public IntPtr hwndParent;
            [MarshalAs(UnmanagedType.LPWStr)] public string pszMessageText;
            [MarshalAs(UnmanagedType.LPWStr)] public string pszCaptionText;
            public IntPtr hbmBanner;
        }
       
        /// <summary>
        /// http://www.pinvoke.net/default.aspx/credui.CredUIPromptForCredentialsW
        /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp
        /// </summary>
        [DllImport("credui", EntryPoint="CredUIPromptForCredentialsW", CharSet=CharSet.Unicode)]
        public static extern ReturnCodes PromptForCredentials(
            ref INFO creditUR,
            string targetName,
            IntPtr reserved1,
            int iError,
            StringBuilder userName,
            int maxUserName,
            StringBuilder password,
            int maxPassword,
            ref int iSave,
            FLAGS flags
            );
       
        /// <summary>
        /// http://www.pinvoke.net/default.aspx/credui.CredUIConfirmCredentials
        /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduiconfirmcredentials.asp
        /// </summary>
        [DllImport("credui.dll", EntryPoint="CredUIConfirmCredentialsW", CharSet=CharSet.Unicode)]
        public static extern ReturnCodes ConfirmCredentials(
            string targetName,
            bool confirm
            );
        }
}

On the next page we've got some examples of how to utilise the CredentialsDialog class.

You might also like...

Comments

Alan Dean I am a UK-based professional software developer. I am also active in the development user community.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Better train people and risk they leave – than do nothing and risk they stay.” - Anonymous