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.

“Most software today is very much like an Egyptian pyramid with millions of bricks piled on top of each other, with no structural integrity, but just done by brute force and thousands of slaves” - Alan Kay