When you take the custom authentication route and write a
UsernameTokenManager (UTM), your implementation of AuthenticateToken
must return the same secret (e.g., password) used on the client side
to generate the hash/signature, depending on which option you use.
As he correctly points out, this makes security experts cringe and hide
under the bed (see Keith Brown’s cringing
where he proposes a solution).
The big issue is that your UsernameTokenManager needs access to the
original cleartext password. But like any good security conscious
developer, you don’t store passwords as cleartext, do you? (I sure hope
not. Bad security conscious developer. Bad!). Hopefully you do something
along the likes of what Keith suggests in his MSDN
For each user, he stores a randomly generated salt value and a hash of
the cleartext password combined with salt value. The salt value is
unique per user.
Keith points out that the secret returned by the AuthenticateToken
method doesn’t have to be the actual cleartext password. It just has to
match the secret sent by the client. So if you store your passwords as
an SHA1 hash, your client just needs to hash the password before
creating the UsernameToken.
However, if you store your password as an SHA1 hash of the cleartext
password + salt value, you’re going to have to do a little more work.
Your client isn’t going to know the salt value for every user, so your
client needs a way to discover that. This may require calling a separate
web method just to query for the salt value given a user name. Service
clients would be required to store that value (probably on a “session”
basis) and use it when calling methods on the main web service.
Below is some sample code for doing just that. This assumes that user
passwords are stored as described in the aforementioned article using
salt and hash (no eggs, but do bring the ketchup). (My apologies for the
ugly formatting, I didn’t want the code to be too wide)
//Make an initial web service call to get the
//the salt value for the user "haacked".
//This should be stored by the client so its
//not called for every method of our main service.
MyServiceWse proxy = new MyServiceWse();
//In order to get the salt value, a special account
//"saltAdmin" is used to call GetSalt(). This account
//only has access to this method.
//This also requires that the client app knows the;
//saltAdmin's salt value up front.
string adminPassword = GetAdminPassword();
//implementation not shown.
= new UsernameToken("saltAdmin", adminPassword
string username = "haacked";
string salt = proxy.GetSalt(username);
// Hash password and salt.
string pw = "Password"; //assume this came from the user.
= new SHA1CryptoServiceProvider();
byte inputBuffer = Encoding.Unicode.GetBytes(pw + salt);
byte result = hashProvider.ComputeHash(inputBuffer);
string hashedPassword = Convert.ToBase64String(result);
//Set up the user's token.
//Notice we the hashed password instead of the cleartext one.
= new UsernameToken(username, hashedPassword
//Make the actual service call.
The AuthenitcateToken method of your custom UsernameTokenManager class
can now just return the hashed password value for the calling user from
your data store and everything will work just fine and security experts
can come out from under the bed.