How to: Sign a SOAP Message by Using a User Name and Password

Web Services Enhancements for Microsoft .NET Framework (WSE) define the UsernameToken class for passing security credentials in the form of a user name and password. Once an instance of the UsernameToken is created, it can be used to sign SOAP messages.

Note

The UsernameToken security token should only be used as a token that identifies the client and not for digital signing or encrypting SOAP messages. When a SOAP message is digitally signed or encrypted by using a UsernameToken security token, it is susceptible to a dictionary attack. Instead, use the UsernameToken security token for identity and an EncryptedKeyToken security token to digitally sign or encrypt the SOAP messages. The <usernameForCertificateSecurity> Element turnkey security assertion uses this model.

The following procedures detail how to use code to sign a SOAP request by using a UsernameToken. A Web service can also sign the SOAP response by using similar steps.

To use code to sign a SOAP message by using a user name and password

  1. Open the Web service or client project in the Microsoft® Visual Studio® 2005 development system.

  2. Add references to the Microsoft.Web.Services3 and System.Web.Services assemblies.

    1. In Solution Explorer, right-click References, and then click Add Reference.
    2. On the .NET tab, select Microsoft.Web.Services3.dll, and then click Select.
    3. On the .NET tab, select System.Web.Services.dll, and then click Select.
    4. Click OK.
  3. Create a custom policy assertion.

    For more details about creating custom policy assertions, see How to: Create a Custom Policy Assertion that Secures SOAP Messages.

  4. Add the following using or Imports directives to the top of the file that contains the Web service client code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Security.Cryptography.X509Certificates;
    
    using Microsoft.Web.Services3;
    using Microsoft.Web.Services3.Design;
    using Microsoft.Web.Services3.Security;
    using Microsoft.Web.Services3.Security.Tokens;
    
  5. In the policy assertion, override the SecureMessage method.

    The following code example overrides the SecureMessage method for the client output SOAP filter.

    Public Overrides Sub SecureMessage(ByVal envelope As SoapEnvelope, ByVal security As Security)
    
    public override void SecureMessage(SoapEnvelope envelope, Security security)
    {
    
  6. Within the SecureMessage method, create a new UsernameToken instance and specify the user name, password, and how the password is sent in the SOAP message.

    The Web service provides a class that derives from the UsernameTokenManager class, which includes the AuthenticateToken method. The AuthenticateToken method is given the user name in the SOAP message, but no form of the user name's password. The AuthenticateToken method is expected to return the user name's password or password equivalent. WSE calls the AuthenticateToken method, and then compares the password or password equivalent to the one received in the SOAP message. The following table explains the options for passing the password in the SOAP message.

    PasswordOption enumeration member Description

    SendNone

    The password is never sent in any form in the SOAP message, but WSE does use the password to sign the SOAP message. A recipient then must provide a password to WSE during the signature verification stage.

    SendHashed

    The SHA-1 hash of the password is sent in the SOAP message. This is the best way to help protect the password. When a SOAP message is received with a UsernameToken, WSE calls the AuthenticateToken method of the class that derives from UsernameTokenManager that is registered in the configuration file. The AuthenticateToken method returns a password or password equivalent, which WSE creates a SHA-1 hash from. That SHA-1 hash is compared to the one in the SOAP message and if they are identical, the hashed password is deemed valid.

    SendPlainText

    The password is always sent in plain text in the SOAP message. This option is recommended only when the UsernameToken is encrypted using a security token or certificate obtained from the target Web service or SSL or a similar protocol is used to connect to the Web service; otherwise, the password could be intercepted. WSE running on the recipient's computer compares the plain text password or password equivalent in the SOAP message to the one returned from the AuthenticateToken method of the class that derives from UsernameTokenManager. If they are identical, the password is deemed valid.

    The following code example sends the password in plain text in the SOAP request.

    Dim userToken As UsernameToken
    userToken = New UsernameToken(userName, passwordEquivalent, _
                PasswordOption.SendPlainText)
    
    UsernameToken userToken;
    userToken = new UsernameToken(userName, passwordEquivalent,
                PasswordOption.SendPlainText);
    
  7. Add the UsernameToken to the WS-Security SOAP header.

    ' Add the token to the SOAP header.
    security.Tokens.Add(userToken)
    
    security.Tokens.Add(userToken);
    
  8. Encrypt the UsernameToken in the WS-Security SOAP header.

    This step is optional. However, if the password is being sent in plain text, it is recommended that the UsernameToken, which contains the password, be encrypted or that SSL or a similar protocol be used to connect to the Web service; otherwise, the password could be intercepted. If the target Web service is authorizing the SOAP request based on the UsernameToken, the password must be sent in plain text.

    The following code example encrypts the UsernameToken by using an X.509 certificate obtained from the target Web service. To see the code that obtains the X.509 certificate in the GetSecurityToken user-defined method, see How to: Encrypt a SOAP Message Using an X.509 Certificate.

    Dim serverToken As X509SecurityToken = GetSecurityToken("CN=WSE2QuickStartClient")
    security.Elements.Add(New _
        EncryptedData(serverToken, "#" + userToken.Id))
    
    X509SecurityToken serverToken = GetSecurityToken("CN=WSE2QuickStartClient");
    security.Elements.Add(new
        EncryptedData(serverToken,"#" + userToken.Id));
    
  9. Create a new instance of the MessageSignature class by using the UsernameToken just added to the WS-Security SOAP header.

    For more information about signing portions of the SOAP message other than the defaults, see Signing Custom SOAP Headers.

    Dim sig As New MessageSignature(userToken)
    
    MessageSignature sig = new MessageSignature(userToken);
    
  10. Add the digital signature to the WS-Security SOAP header.

    security.Elements.Add(sig)
    
    security.Elements.Add(sig);
    

Example

The following code example signs a SOAP message by using a UsernameToken security token.

Public Overrides Sub SecureMessage(ByVal envelope As SoapEnvelope, ByVal security As Security)
    Dim userToken As UsernameToken
    userToken = New UsernameToken(userName, passwordEquivalent, _
                PasswordOption.SendPlainText)

    ' Add the token to the SOAP header.
    security.Tokens.Add(userToken)

    Dim serverToken As X509SecurityToken = GetSecurityToken("CN=WSE2QuickStartClient")
    security.Elements.Add(New _
        EncryptedData(serverToken, "#" + userToken.Id))

    ' Sign the SOAP message by using the UsernameToken.
    Dim sig As New MessageSignature(userToken)
    security.Elements.Add(sig)
End Sub 'SecureMessage
Public Function GetSecurityToken(ByVal subjectName As String) As X509SecurityToken
    Dim securityToken As X509SecurityToken = Nothing

    Dim store As X509Store = New X509Store(StoreName.My, _
      StoreLocation.CurrentUser)
    store.Open(OpenFlags.ReadOnly)

    Try

        Dim certs As X509Certificate2Collection = _
          store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, _
          subjectName, False)


        Dim cert As X509Certificate2
        If certs.Count = 1 Then
            cert = certs(0)
            securityToken = New X509SecurityToken(cert)

        Else
            securityToken = Nothing
        End If
    Catch ex As Exception
        Console.WriteLine(ex.ToString())
    Finally
        If Not (store Is Nothing) Then
            store.Close()
        End If
    End Try
    Return securityToken
End Function
public override void SecureMessage(SoapEnvelope envelope, Security security)
{
    UsernameToken userToken;
    userToken = new UsernameToken(userName, passwordEquivalent,
                PasswordOption.SendPlainText);
    
    // Add the token to the SOAP header.
    security.Tokens.Add(userToken);
    X509SecurityToken serverToken = GetSecurityToken("CN=WSE2QuickStartClient");
    security.Elements.Add(new
        EncryptedData(serverToken,"#" + userToken.Id));

    // Sign the SOAP message by using the UsernameToken.
    MessageSignature sig = new MessageSignature(userToken);
    security.Elements.Add(sig);

}

public X509SecurityToken GetSecurityToken(string subjectName)
{
    X509SecurityToken securityToken = null;
    
    X509Store store = new X509Store(StoreName.My,
      StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);
    
    try
    {
        
        X509Certificate2Collection certs =
            store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName,
            subjectName, false);

        
        
        X509Certificate2 cert;
        if (certs.Count == 1)
        {
            cert = certs[0];
            securityToken = new X509SecurityToken(cert);
        }
        
        else
            securityToken = null;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally
    {
        if (store != null)
            store.Close();
    }
    return securityToken;
}

See Also

Tasks

How to: Verify Digital Signatures of SOAP Messages Signed Using a User Name and Password
How to: Digitally Sign a SOAP Message

Reference

UsernameToken
PasswordOption

Other Resources

Signing Custom SOAP Headers