In my current project, we are using Windows Communication Foundation to create a client/server distributed system. In this system, we have a fairly typical User object - it has a username, password, and other bits of information that are required for the user to operate in the system. One of the business rules that we put in place, to help protect the overall security of the system, was that a password should never be human readable once it has been typed into the system. A key point in this statement is that we are not saying "once it's stored in the database" - we are saying as soon as you type it in to a textbox on the form, it should not be human readable.
Implementation - a PasswordHasher class
To implement this business rule, we created a PasswordHasher that hashes any string we pass into it and appropriately returns a non-human readable string of hashed garbage. We then call this method in the property set for the user's password.
public class User
{
private string _password;
[DataMember]
public string Password
{
get { return _password; }
set { _password = PasswordHasher.Hash(value); }
}
}
Looks like a straightforward and good implementation of the business rule, in my mind - and it worked well for a while.
Problem - corrupt password data
After a few weeks of this working, though, we all forgot about it and moved on to other things. Then we turned on the client/server setup and started remoting our objects through WCF... suddenly the add/edit user process was broken and we could not log in with any new users that we created. After a few hours of multiple developers trying to figure out why this wasn't working, we noticed that the password was getting hashed by the User object, in the client GUI as expected and that it was getting re-hashed (the hash was getting hashed) once it was sent across the wire to the server. A few minutes later, we realized that the culprit was the [DataMember] attribute.
When a property is marked as [DataMember], you are required to provide both a get and set portion of the property. WCF, internally, uses the get and set portions of the property to perform serialization and deserialization. In other words, when the object is serialized to be sent across the wire, the get portion is called. Then, on the other side of the wire when the serialized data needs to be deserialized into an actual object, the set portion of the property is called and the serialized value is passed into the set portion. When this happens, any and all code in the set portion is executed - thus our PasswordHasher.Hash call was made and the already hashed password was hashed again, causing complete corruption of the data and making it impossible to login with that user.
Solution - mark the field as [DataMember]
Fortunately, the solution was simple - move the [DataMember] attribute off the property and onto the private field. As noted in my previous posts on WCF, you can mark a private field as a [DataMember] and WCF will use the field directly, to serialize and deserialize the object. One simple change, and our business rule is in tact and we can use WCF to remote the object without corrupting the data.
public class User
{
[DataMember]
private string _password;
public string Password
{
get { return _password; }
set { _password = PasswordHasher.Hash(value); }
}
}
Lesson learned
Be careful with your WCF implementation and the attribute usage associated with that implementation. Just because you have a property with a get/set, that doesn't mean you should automatically mark the property as the [DataMember]. Be sure to account for business rules and data validation that occurs within your get/set and weigh in the option of marking the underlying field as the [DataMember].