Is there an agreed-upon "best practice" for PasswordBox binding?

Topics: Getting Started, UI Architecture
Mar 14, 2012 at 12:31 PM

Hi there, I'm trying to implement a login window in my Caliburn.Micro project and I'm having some difficulty trying to get my head around the best way in which to bind to a PasswordBox. I've looked through quite a lot of posts and I can't see anything that's really a) very recent or b) "recommended". Is the situation still the same now?

Thanks!

Mar 14, 2012 at 1:08 PM

If i'm not mistaken PasswordBox isn't meant to be bound to for security reasons.

Mar 14, 2012 at 1:40 PM

So how does one do an MVVM Login window without using a PasswordBox, or without binding it!?

Mar 14, 2012 at 1:48 PM

Well, you can reference it by name.  If your login viewmodel inherits IViewAware, you can override OnViewAttached or OnViewLoaded, they both include an instance of the View which would allow you to get the named PasswordBox.

 

private object View;

protected override void OnViewLoaded(object view) {
  View = view;
  base.OnViewLoaded(view); 
}

private void DoSomething(){
  var passwordBox = ((YourViewType)View).passwordBox;
}

 

Mar 14, 2012 at 2:41 PM

I can do that agreed, but I'd like entry validation and the ability to enable controls as & when a password is entered by the user... That kind of thing can only be done with binding, can't it?

Mar 14, 2012 at 2:46 PM

My approach uses a custom trigger to synchronize the PasswordBox.Password field with the view-model counterpart.

The issue about security can be easily overcome using a SecureString in the view-model (as I did) to avoid to save the password as a plain string.

For the sake of completeness, I decided to provide synchronization for both SecureString and String back-end.

Note that I just tested it in a WPF application.

Extensions:

 

namespace Extensions
{
    #region Namespaces
    using System.Runtime.InteropServices;
    using System.Security;

    #endregion

    /// <summary>
    ///   Static class used to store extension functions.
    /// </summary>
    public static class MyExtensions
    {
        #region Static Methods
        /// <summary>
        ///   Converts a secure string into a <see cref = "string" />.
        /// </summary>
        /// <param name = "secureString">The secure string.</param>
        /// <returns>The string containing the content of the <paramref name = "secureString" />.</returns>
        public static string ToPlainString(this SecureString secureString)
        {
            var ptr = Marshal.SecureStringToGlobalAllocUnicode(secureString);
            try
            {
                // Unsecure managed string
                return Marshal.PtrToStringUni(ptr);
            }
            finally
            {
                Marshal.ZeroFreeGlobalAllocUnicode(ptr);
            }
        }

        /// <summary>
        ///     Attaches a binding to this element, based on the provided path and source.
        /// </summary>
        /// <param name="target"> The target. </param>
        /// <param name="property"> The property. </param>
        /// <param name="source"> The source of the binding. </param>
        /// <param name="path"> The source property path. </param>
        /// <param name="converter"> The converter. </param>
        /// <param name="converterParameter"> The converter parameter. </param>
        /// <param name="mode"> The binding mode. </param>
        /// <returns> Records the conditions of the binding. This return value can be useful for error checking. </returns>
        public static BindingExpressionBase SetBinding(this DependencyObject target, DependencyProperty property, object source, PropertyPath path, IValueConverter converter = null, object converterParameter = null, BindingMode mode = BindingMode.Default)
        {
            var binding = new Binding
                          {
                                  Path = path,
                                  Source = source,
                                  Converter = converter,
                                  ConverterParameter = converterParameter,
                                  Mode = mode
                          };

            return BindingOperations.SetBinding(target, property, binding);
        }
        #endregion
    }
}

 

 

Custom triggers:

SecureString

 

namespace MVVM.Interactivity
{
    #region Namespaces
    using System.Security;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Interactivity;
    using Extensions;

    #endregion

    /// <summary>
    ///     Class used to define an action used to synchronize a password.
    /// </summary>
    public class SynchronizeSecurePassword : TriggerAction<PasswordBox>
    {
        #region Static Fields
        /// <summary>
        ///     The Path dependency property.
        /// </summary>
        public static readonly DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(PropertyPath), typeof(SynchronizeSecurePassword), new FrameworkPropertyMetadata(null, OnPathChanged));

        /// <summary>
        ///     The CurrentPassword dependency property.
        /// </summary>
        public static readonly DependencyProperty CurrentPasswordProperty = DependencyProperty.Register("CurrentPassword", typeof(SecureString), typeof(SynchronizeSecurePassword), new FrameworkPropertyMetadata(null, OnCurrentPasswordChanged));
        #endregion

        #region Static Methods
        /// <summary>
        ///     Handles changes to the Path property.
        /// </summary>
        /// <param name="obj"> The dependency object. </param>
        /// <param name="e"> The <see cref="System.Windows.DependencyPropertyChangedEventArgs" /> instance containing the event data. </param>
        private static void OnPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            ((SynchronizeSecurePassword)obj).OnPathChanged(e);
        }

        /// <summary>
        ///     Handles changes to the CurrentPassword property.
        /// </summary>
        /// <param name="obj"> The dependency object. </param>
        /// <param name="e"> The <see cref="System.Windows.DependencyPropertyChangedEventArgs" /> instance containing the event data. </param>
        private static void OnCurrentPasswordChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            ((SynchronizeSecurePassword)obj).OnCurrentPasswordChanged(e);
        }
        #endregion

        #region Fields
        /// <summary>
        ///     Flag used to determine if the update proces was started from the action itself.
        /// </summary>
        private bool _isUpdating;
        #endregion

        #region Properties
        /// <summary>
        ///     Gets or sets the path used to identify the property to use as a password backend.
        /// </summary>
        /// <value> The path used to identify the property to use as a password backend. </value>
        public PropertyPath Path
        {
            get { return (PropertyPath)GetValue(PathProperty); }
            set { SetValue(PathProperty, value); }
        }

        /// <summary>
        ///     Gets or sets the current password.
        /// </summary>
        /// <value> The current password. </value>
        private SecureString CurrentPassword
        {
            get { return (SecureString)GetValue(CurrentPasswordProperty); }
            set { SetValue(CurrentPasswordProperty, value); }
        }
        #endregion

        /// <summary>
        ///     Provides derived classes an opportunity to handle changes to the Path property.
        /// </summary>
        /// <param name="e"> The <see cref="System.Windows.DependencyPropertyChangedEventArgs" /> instance containing the event data. </param>
        protected virtual void OnPathChanged(DependencyPropertyChangedEventArgs e)
        {
            this.SetBinding(CurrentPasswordProperty, AssociatedObject, Path, mode: BindingMode.TwoWay);
        }

        /// <summary>
        ///     Provides derived classes an opportunity to handle changes to the CurrentPassword property.
        /// </summary>
        /// <param name="e"> The <see cref="System.Windows.DependencyPropertyChangedEventArgs" /> instance containing the event data. </param>
        protected virtual void OnCurrentPasswordChanged(DependencyPropertyChangedEventArgs e)
        {
            if (!_isUpdating)
            {
                _isUpdating = true;
                var newSecurePassword = (SecureString)e.NewValue;
                AssociatedObject.Password = newSecurePassword.ToPlainString();
                _isUpdating = false;
            }
        }

        /// <summary>
        ///     Called after the action is attached to an AssociatedObject.
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();
            this.SetBinding(CurrentPasswordProperty, AssociatedObject, Path, mode: BindingMode.TwoWay);
        }

        /// <summary>
        ///     Called when the action is being detached from its AssociatedObject, but before it has actually occurred.
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();
            BindingOperations.ClearBinding(this, CurrentPasswordProperty);
        }

        /// <summary>
        ///     Invokes the action.
        /// </summary>
        /// <param name="parameter"> The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference. </param>
        protected override void Invoke(object parameter)
        {
            if (!_isUpdating)
            {
                _isUpdating = true;
                CurrentPassword = AssociatedObject.SecurePassword;
                _isUpdating = false;
            }
        }
    }
}

 

 

String

namespace MVVM.Interactivity
{
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Interactivity;
    using Extensions;

    /// <summary>
    ///     Class used to define an action used to synchronize a password.
    /// </summary>
    public class SynchronizePassword : TriggerAction<PasswordBox>
    {
        #region Static Fields
        /// <summary>
        ///     The Path dependency property.
        /// </summary>
        public static readonly DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(PropertyPath), typeof(SynchronizePassword), new FrameworkPropertyMetadata(null, OnPathChanged));

        /// <summary>
        ///     The CurrentPassword dependency property.
        /// </summary>
        public static readonly DependencyProperty CurrentPasswordProperty = DependencyProperty.Register("CurrentPassword", typeof(string), typeof(SynchronizePassword), new FrameworkPropertyMetadata(null, OnCurrentPasswordChanged));
        #endregion

        #region Static Methods
        /// <summary>
        ///     Handles changes to the Path property.
        /// </summary>
        /// <param name="obj"> The dependency object. </param>
        /// <param name="e"> The <see cref="System.Windows.DependencyPropertyChangedEventArgs" /> instance containing the event data. </param>
        private static void OnPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            ((SynchronizePassword)obj).OnPathChanged(e);
        }

        /// <summary>
        ///     Handles changes to the CurrentPassword property.
        /// </summary>
        /// <param name="obj"> The dependency object. </param>
        /// <param name="e"> The <see cref="System.Windows.DependencyPropertyChangedEventArgs" /> instance containing the event data. </param>
        private static void OnCurrentPasswordChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            ((SynchronizePassword)obj).OnCurrentPasswordChanged(e);
        }
        #endregion

        #region Fields
        /// <summary>
        ///     Flag used to determine if the update proces was started from the action itself.
        /// </summary>
        private bool _isUpdating;
        #endregion

        #region Properties
        /// <summary>
        ///     Gets or sets the path used to identify the property to use as a password backend.
        /// </summary>
        /// <value> The path used to identify the property to use as a password backend. </value>
        public PropertyPath Path
        {
            get { return (PropertyPath)GetValue(PathProperty); }
            set { SetValue(PathProperty, value); }
        }

        /// <summary>
        ///     Gets or sets the current password.
        /// </summary>
        /// <value> The current password. </value>
        private string CurrentPassword
        {
            get { return (string)GetValue(CurrentPasswordProperty); }
            set { SetValue(CurrentPasswordProperty, value); }
        }
        #endregion

        /// <summary>
        ///     Provides derived classes an opportunity to handle changes to the Path property.
        /// </summary>
        /// <param name="e"> The <see cref="System.Windows.DependencyPropertyChangedEventArgs" /> instance containing the event data. </param>
        protected virtual void OnPathChanged(DependencyPropertyChangedEventArgs e)
        {
            this.SetBinding(CurrentPasswordProperty, AssociatedObject, Path, mode: BindingMode.TwoWay);
        }

        /// <summary>
        ///     Provides derived classes an opportunity to handle changes to the CurrentPassword property.
        /// </summary>
        /// <param name="e"> The <see cref="System.Windows.DependencyPropertyChangedEventArgs" /> instance containing the event data. </param>
        protected virtual void OnCurrentPasswordChanged(DependencyPropertyChangedEventArgs e)
        {
            if (!_isUpdating)
            {
                _isUpdating = true;
                var newPassword = (string)e.NewValue;
                AssociatedObject.Password = newPassword;
                _isUpdating = false;
            }
        }

        /// <summary>
        ///     Called after the action is attached to an AssociatedObject.
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();
            this.SetBinding(CurrentPasswordProperty, AssociatedObject, Path, mode: BindingMode.TwoWay);
        }

        /// <summary>
        ///     Called when the action is being detached from its AssociatedObject, but before it has actually occurred.
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();
            BindingOperations.ClearBinding(this, CurrentPasswordProperty);
        }

        /// <summary>
        ///     Invokes the action.
        /// </summary>
        /// <param name="parameter"> The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference. </param>
        protected override void Invoke(object parameter)
        {
            if (!_isUpdating)
            {
                _isUpdating = true;
                CurrentPassword = AssociatedObject.Password;
                _isUpdating = false;
            }
        }
    }
}

Convention:

namespace MVVM
{
    #region Namespaces
    using System;
    using System.Collections.Generic;
    using System.Security;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Interactivity;
    using MVVM.Interactivity;
    using Caliburn.Micro;
    using EventTrigger = System.Windows.Interactivity.EventTrigger;

    #endregion

    /// <summary>
    ///     Static class used to store custom conventions.
    /// </summary>
    public static class Conventions
    {
        #region Static Methods
        /// <summary>
        ///     Applies all supported custom conventions.
        /// </summary>
        public static void Apply()
        {
            ApplyPasswordBoxConvention();
        }

        /// <summary>
        ///     Applies the default convention for <see cref="PasswordBox" /> objects.
        /// </summary>
        public static void ApplyPasswordBoxConvention()
        {
            ConventionManager.AddElementConvention<PasswordBox>(null, "Password", "PasswordChanged").ApplyBinding = (viewModelType, path, property, element, convention) =>
                                                                                                                    {
                                                                                                                        var passwordBox = (PasswordBox)element;
                                                                                                                        if (typeof(SecureString).IsAssignableFrom(property.PropertyType))
                                                                                                                        {
                                                                                                                            var eventTrigger = new EventTrigger("PasswordChanged");
                                                                                                                            eventTrigger.Actions.Add(new SynchronizeSecurePassword
                                                                                                                                                     {
                                                                                                                                                             Path = new PropertyPath(string.Format("DataContext.{0}", property.Name))
                                                                                                                                                     });
                                                                                                                            Interaction.GetTriggers(passwordBox).Add(eventTrigger);
                                                                                                                            return true;
                                                                                                                        }
                                                                                                                        if (typeof(string).IsAssignableFrom(property.PropertyType))
                                                                                                                        {
                                                                                                                            var eventTrigger = new EventTrigger("PasswordChanged");
                                                                                                                            eventTrigger.Actions.Add(new SynchronizePassword
                                                                                                                                                     {
                                                                                                                                                             Path = new PropertyPath(string.Format("DataContext.{0}", property.Name))
                                                                                                                                                     });
                                                                                                                            Interaction.GetTriggers(passwordBox).Add(eventTrigger);
                                                                                                                            return true;
                                                                                                                        }

                                                                                                                        return false;
                                                                                                                    };
        }
        #endregion
    }
}

Mar 14, 2012 at 2:46 PM

Well, there are some implementations of databinding to a passwordbox out there, but it goes against everything the PasswordBox stands for since storing passwords in plaintext in memory is just asking for it to be stolen.  If you look on stackoverflow there's been plenty of discussion on it.

Mar 14, 2012 at 3:09 PM

I never said I needed it in plaintext! A SecureString would be perfect! Thanks, BladeWise I'll take a proper look through it in a bit, but on initial glance, looks good!

Jun 3, 2015 at 8:10 PM
If not supported why is this example touted on the http://caliburnmicro.com/ doc page? Sure would like to know how that can be made to work as I'm unable to do so.

<StackPanel>
<TextBox x:Name="Username" />
<PasswordBox x:Name="Password" />
<Button x:Name="Login" Content="Log in" />
</StackPanel>