It sure is great to see many people beginning to experiment and use Web Services Enhancements (WSE) 2.0. Many people I speak to and come across today are still trying to grasp the security features of WSE.
I had blogged about this earlier in my old weblog host here. However, I will just summed it up for interested parties in this post.
...While the Documentation of WSE 2.0 teaches on how to authenticate a usernameToken from a client request against a database query [I print it here for your benefit]:
Protected Overrides Function AuthenticateToken(userName As UsernameToken) As String
'BLAH BLAH BLAH
'You are supposed to be reading from a returned Database query based on the UsernameToken.userName property.
'Then you are supposed to return the Password that is returned from the DB Query here
However, it doesnt touch on one of the best practices of Security of storing passwords into a database which is to hash userpasswords and then store it in the DB. So the question is how do we compare then ?
Bearing in mind the constraints of
- Passwords must be sent from the client in PlainText Form so the service can hash it and compare against the database
- Passwords cannot be sent in Hash Digest form as the service would have NO idea on how to re-engineer the actual password from the Hash Digest
I suggest a couple of alternatives to solve this problem....Most of what you choose will depend on very much how much control you have over the client
- SSL it [The ugliest, expensive and least scalable way...but requires the least coding work]
- Send the username, password in ClearText Via the usernameToken. However, encrypt this entire usernameToken before sending it over. The pipelines of WSE will handle the decryption for you. Once it reaches your service application, you will know what the ClearText password is. Hash it and then do a straight comparison of the Hash Digest against the one returned from the Database. This method assumes that the server has the Private Key portion of an asymmetric token such as a X509 Digital Certificate or even the service has a X509 Digital Certificate to begin with and it also assumes the knowledge of the Hash Algorithm method employed by the user DB.
While the documentation may be poor, the extensibility of the UsernameTokenManager is great ! I will reproduce snippets of option  for your benefit
Dim a As localhost.IndexWse = New localhost.IndexWse
'Send PlainText Password across so that the Service can hash Digest
'this value for comparison later
Dim ut As UsernameToken = New UsernameToken _
("TestAccount", "TestPassword", PasswordOption.SendPlainText)
'Include the usernameToken in the message so the Server can verify that
'this usernameToken actually came from you
'Blah - Find the Service Public Key of its X509 Cert so only the Service
'can decrypt and read this Token
Dim store As X509CertificateStore = _
Dim cert As X509Certificate = store.FindCertificateByKeyIdentifier _
Dim tok As X509SecurityToken = New X509SecurityToken(cert)
'Sign and Encrypt everything in the message. Beware of the Payload !
a.RequestSoapContext.Security.Elements.Add(New EncryptedData(tok, "#" & ut.Id))
Public Class CustomUsernameTokenManager
Protected Overrides Function AuthenticateToken(ByVal token As UsernameToken) As String
Dim pw As String = token.Password
'Employ the same Hash function on the password that you deployed saving the passwords
'into the User DB
Dim hpw as string = userDBHash(pw)
'Compare the hpw result with the one returned from the oledbReader from the Database
'with the username as the Reference
'If it matches, return the same password from the Token
Return "A Password that is impossible to GUESS"
End Function 'AuthenticateToken
End Class 'CustomUsernameTokenManager
If you check your diagnostics and the SOAP Message Tracing Files, you will notice that the entire usernameToken is encrypted and nothing is sent over in ClearText even tho the passwordOption is set to SendPlainText.