How can I pass a username/password in the header to a SOAP WCF Service

I’m trying to consume a third-party web service
https://staging.identitymanagement.lexisnexis.com/identity-proofing/services/identityProofingServiceWS/v2?wsdl

I already added it as a service reference but I’m not sure how to pass the credentials for the header.

How can I make the header request match this format?

<soapenv:Header>
    <wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <wsse:UsernameToken wsu:Id="UsernameToken-49" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
            <wsse:Username>12345/userID</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/ oasis-200401-wss-username-token-profile-1.0#PasswordText">password123</wsse:Password>
            <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">d+VxCZX1cH/ieMkKEr/ofA==</wsse:Nonce>
            <wsu:Created>2012-08-04T20:25:04.038Z</wsu:Created>
        </wsse:UsernameToken>
    </wsse:Security>
</soapenv:Header>

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

The answers above are so wrong! DO NOT add custom headers. Judging from your sample xml, it is a standard WS-Security header. WCF definitely supports it out of the box. When you add a service reference you should have basicHttpBinding binding created for you in the config file. You will have to modify it to include security element with mode TransportWithMessageCredential and message element with clientCredentialType = UserName:

<basicHttpBinding>
  <binding name="usernameHttps">
    <security mode="TransportWithMessageCredential">
      <message clientCredentialType="UserName"/>
    </security>
  </binding>
</basicHttpBinding>

The config above is telling WCF to expect userid/password in the SOAP header over HTTPS. Then you can set id/password in your code before making a call:

var service = new MyServiceClient();
service.ClientCredentials.UserName.UserName = "username";
service.ClientCredentials.UserName.Password = "password";

Unless this particular service provider deviated from the standard, it should work.

Method 2

There is probably a smarter way, but you can add the headers manually like this:

var client = new IdentityProofingService.IdentityProofingWSClient();

using (new OperationContextScope(client.InnerChannel))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(
        new SecurityHeader("UsernameToken-49", "12345/userID", "password123"));
    client.invokeIdentityService(new IdentityProofingRequest());
}

Here, SecurityHeader is a custom implemented class, which needs a few other classes since I chose to use attributes to configure the XML serialization:
public class SecurityHeader : MessageHeader
{
    private readonly UsernameToken _usernameToken;

    public SecurityHeader(string id, string username, string password)
    {
        _usernameToken = new UsernameToken(id, username, password);
    }

    public override string Name
    {
        get { return "Security"; }
    }

    public override string Namespace
    {
        get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
    }

    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(UsernameToken));
        serializer.Serialize(writer, _usernameToken);
    }
}


[XmlRoot(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public class UsernameToken
{
    public UsernameToken()
    {
    }

    public UsernameToken(string id, string username, string password)
    {
        Id = id;
        Username = username;
        Password = new Password() {Value = password};
    }

    [XmlAttribute(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
    public string Id { get; set; }

    [XmlElement]
    public string Username { get; set; }

    [XmlElement]
    public Password Password { get; set; }
}

public class Password
{
    public Password()
    {
        Type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";
    }

    [XmlAttribute]
    public string Type { get; set; }

    [XmlText]
    public string Value { get; set; }
}

I have not added the Nonce bit to the UsernameToken XML, but it is very similar to the Password one. The Created element also needs to be added still, but it’s a simple [XmlElement].

Method 3

Adding a custom hard-coded header may work (it also may get rejected at times) but it is totally the wrong way to do it. The purpose of the WSSE is security. Microsoft released the Microsoft Web Services Enhancements 2.0 and subsequently the WSE 3.0 for this exact reason. You need to install this package (http://www.microsoft.com/en-us/download/details.aspx?id=14089).

The documentation is not easy to understand, especially for those who have not worked with SOAP and the WS-Addressing. First of all the “BasicHttpBinding” is Soap 1.1 and it will not give you the same message header as the WSHttpBinding. Install the package and look at the examples. You will need to reference the DLL from WSE 3.0 and you will also need to setup your message correctly. There are a huge number or variations on the WS Addressing header. The one you are looking for is the UsernameToken configuration.

This is a longer explanation and I should write something up for everyone since I cannot find the right answer anywhere. At a minimum you need to start with the WSE 3.0 package.

Method 4

Obviously it has been some years this post has been alive – but the fact is I did find it when looking for a similar issue. In our case, we had to add the username / password info to the Security header. This is different from adding header info outside of the Security headers.

The correct way to do this (for custom bindings / authenticationMode=”CertificateOverTransport”) (as on the .Net framework version 4.6.1), is to add the Client Credentials as usual :

    client.ClientCredentials.UserName.UserName = "[username]";
    client.ClientCredentials.UserName.Password = "[password]";

and then add a “token” in the security binding element – as the username / pwd credentials would not be included by default when the authentication mode is set to certificate.

You can set this token like so:

    //Get the current binding 
    System.ServiceModel.Channels.Binding binding = client.Endpoint.Binding;
    //Get the binding elements 
    BindingElementCollection elements = binding.CreateBindingElements();
    //Locate the Security binding element
    SecurityBindingElement security = elements.Find<SecurityBindingElement>();

    //This should not be null - as we are using Certificate authentication anyway
    if (security != null)
    {
    UserNameSecurityTokenParameters uTokenParams = new UserNameSecurityTokenParameters();
    uTokenParams.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
security.EndpointSupportingTokenParameters.SignedEncrypted.Add(uTokenParams);
    }

   client.Endpoint.Binding = new CustomBinding(elements.ToArray());

That should do it. Without the above code (to explicitly add the username token), even setting the username info in the client credentials may not result in those credentials passed to the Service.

Method 5

Answers that suggest that the header provided in the question are supported out of the box by WCF are incorrect. The header in the question contains a Nonce and a Created timestamp in the UsernameToken, which is an official part of the WS-Security specification that WCF does not support. WCF only supports username and password out of the box.

If all you need to do is add a username and password, then Sergey’s answer is the least-effort approach. If you need to add any other fields, you will need to supply custom classes to support them.

A somewhat more elegant approach that I found was to override the ClientCredentials, ClientCredentialsSecurityTokenManager and WSSecurityTokenizer classes to support the additional properties. I’ve provided a link to the blog post where the approach is discussed in detail, but here is the sample code for the overrides:

public class CustomCredentials : ClientCredentials
{
    public CustomCredentials()
    { }

    protected CustomCredentials(CustomCredentials cc)
        : base(cc)
    { }

    public override System.IdentityModel.Selectors.SecurityTokenManager CreateSecurityTokenManager()
    {
        return new CustomSecurityTokenManager(this);
    }

    protected override ClientCredentials CloneCore()
    {
        return new CustomCredentials(this);
    }
}

public class CustomSecurityTokenManager : ClientCredentialsSecurityTokenManager
{
    public CustomSecurityTokenManager(CustomCredentials cred)
        : base(cred)
    { }

    public override System.IdentityModel.Selectors.SecurityTokenSerializer CreateSecurityTokenSerializer(System.IdentityModel.Selectors.SecurityTokenVersion version)
    {
        return new CustomTokenSerializer(System.ServiceModel.Security.SecurityVersion.WSSecurity11);
    }
}

public class CustomTokenSerializer : WSSecurityTokenSerializer
{
    public CustomTokenSerializer(SecurityVersion sv)
        : base(sv)
    { }

    protected override void WriteTokenCore(System.Xml.XmlWriter writer,
                                            System.IdentityModel.Tokens.SecurityToken token)
    {
        UserNameSecurityToken userToken = token as UserNameSecurityToken;

        string tokennamespace = "o";

        DateTime created = DateTime.Now;
        string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");

        // unique Nonce value - encode with SHA-1 for 'randomness'
        // in theory the nonce could just be the GUID by itself
        string phrase = Guid.NewGuid().ToString();
        var nonce = GetSHA1String(phrase);

        // in this case password is plain text
        // for digest mode password needs to be encoded as:
        // PasswordAsDigest = Base64(SHA-1(Nonce + Created + Password))
        // and profile needs to change to
        //string password = GetSHA1String(nonce + createdStr + userToken.Password);

        string password = userToken.Password;

        writer.WriteRaw(string.Format(
        "<{0}:UsernameToken u:Id="" + token.Id +
        "" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">" +
        "<{0}:Username>" + userToken.UserName + "</{0}:Username>" +
        "<{0}:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">" +
        password + "</{0}:Password>" +
        "<{0}:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">" +
        nonce + "</{0}:Nonce>" +
        "<u:Created>" + createdStr + "</u:Created></{0}:UsernameToken>", tokennamespace));
    }

    protected string GetSHA1String(string phrase)
    {
        SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
        byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
        return Convert.ToBase64String(hashedDataBytes);
    }

}

Before creating the client, you create the custom binding and manually add the security, encoding and transport elements to it. Then, replace the default ClientCredentials with your custom implementation and set the username and password as you would normally:

var security = TransportSecurityBindingElement.CreateUserNameOverTransportBindingElement();
    security.IncludeTimestamp = false;
    security.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256;
    security.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;

var encoding = new TextMessageEncodingBindingElement();
encoding.MessageVersion = MessageVersion.Soap11;

var transport = new HttpsTransportBindingElement();
transport.MaxReceivedMessageSize = 20000000; // 20 megs

binding.Elements.Add(security);
binding.Elements.Add(encoding);
binding.Elements.Add(transport);

RealTimeOnlineClient client = new RealTimeOnlineClient(binding,
    new EndpointAddress(url));

    client.ChannelFactory.Endpoint.EndpointBehaviors.Remove(client.ClientCredentials);
client.ChannelFactory.Endpoint.EndpointBehaviors.Add(new CustomCredentials());

client.ClientCredentials.UserName.UserName = username;
client.ClientCredentials.UserName.Password = password;

Method 6

Suppose you have service reference of the name localhost in your web.config so you can go as follows

localhost.Service objWebService = newlocalhost.Service();
localhost.AuthSoapHd objAuthSoapHeader = newlocalhost.AuthSoapHd();
string strUsrName =ConfigurationManager.AppSettings["UserName"];
string strPassword =ConfigurationManager.AppSettings["Password"];

objAuthSoapHeader.strUserName = strUsrName;
objAuthSoapHeader.strPassword = strPassword;

objWebService.AuthSoapHdValue =objAuthSoapHeader;
string str = objWebService.HelloWorld();

Response.Write(str);

Method 7

Suppose you are calling a web service using HttpWebRequest and HttpWebResponse, because .Net client doest support the structure of the WSLD that your are trying to consume.

In that case you can add the security credentials on the headers like:

<soap:Envelpe>
<soap:Header>
    <wsse:Security soap:mustUnderstand='true' xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'><wsse:UsernameToken wsu:Id='UsernameToken-3DAJDJSKJDHFJASDKJFKJ234JL2K3H2K3J42'><wsse:Username>YOU_USERNAME/wsse:Username><wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'>YOU_PASSWORD</wsse:Password><wsse:Nonce EncodingType='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary'>3WSOKcKKm0jdi3943ts1AQ==</wsse:Nonce><wsu:Created>2015-01-12T16:46:58.386Z</wsu:Created></wsse:UsernameToken></wsse:Security>
</soapHeather>
<soap:Body>
</soap:Body>


</soap:Envelope>

You can use SOAPUI to get the wsse Security, using the http log.

Be careful because it is not a safe scenario.

Method 8

I got a better method from here: WCF: Creating Custom Headers, How To Add and Consume Those Headers

Client Identifies Itself
The goal here is to have the client provide some sort of information
which the server can use to determine who is sending the message. The
following C# code will add a header named ClientId:

var cl = new ActiveDirectoryClient();

var eab = new EndpointAddressBuilder(cl.Endpoint.Address);

eab.Headers.Add( AddressHeader.CreateAddressHeader("ClientId",       // Header Name
                                                   string.Empty,     // Namespace
                                                    "OmegaClient")); // Header Value
cl.Endpoint.Address = eab.ToEndpointAddress();

// Now do an operation provided by the service.
cl.ProcessInfo("ABC");

What that code is doing is adding an endpoint header named ClientId
with a value of OmegaClient to be inserted into the soap header
without a namespace.

Custom Header in Client’s Config File
There is an alternate way of
doing a custom header. That can be achieved in the Xml config file of
the client where all messages sent by specifying the custom header as
part of the endpoint as so:

<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IActiveDirectory" />
            </basicHttpBinding>
        </bindings>
        <client>
          <endpoint address="http://localhost:41863/ActiveDirectoryService.svc"
              binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IActiveDirectory"
              contract="ADService.IActiveDirectory" name="BasicHttpBinding_IActiveDirectory">
            <headers>
              <ClientId>Console_Client</ClientId>
            </headers>
          </endpoint>
        </client>
    </system.serviceModel>
</configuration>

Method 9

I added customBinding to the web.config.

<configuration>
  <system.serviceModel>
    <bindings>
      <customBinding>
        <binding name="CustomSoapBinding">
          <security includeTimestamp="false"
                    authenticationMode="UserNameOverTransport"
                    defaultAlgorithmSuite="Basic256"
                    requireDerivedKeys="false"
                    messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
          </security>
          <textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
          <httpsTransport maxReceivedMessageSize="2000000000"/>
        </binding>
      </customBinding>
    </bindings>
    <client>
      <endpoint address="https://test.com:443/services/testService"
                binding="customBinding"
                bindingConfiguration="CustomSoapBinding"
                contract="testService.test"
                name="test" />
    </client>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0"
                      sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

After adding customBinding, I can pass username and password to client service like as follows:
service.ClientCridentials.UserName.UserName = "testUser";
service.ClientCridentials.UserName.Password = "testPass";

In this way you can pass username, password in the header to a SOAP WCF Service.

Method 10

If this is in relation to this Peoplesoft issue: https://support.oracle.com/knowledge/PeopleSoft%20Enterprise/2370907_1.html

I needed to set the attribute on the Soap Password, where previously the attribute was not being set on that tag.

I simply set the MessageSecurityVersion on my Custom Binding:

  CustomBinding customBinding = new CustomBinding();
            customBinding.Name = endpointName;
            customBinding.CloseTimeout = TimeSpan.FromMinutes(1);
            customBinding.OpenTimeout = TimeSpan.FromMinutes(1);
            customBinding.SendTimeout = TimeSpan.FromMinutes(20);
            customBinding.ReceiveTimeout = TimeSpan.FromMinutes(20);

            TextMessageEncodingBindingElement textMessageElement = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
            customBinding.Elements.Add(textMessageElement);

            TransportSecurityBindingElement securityElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
            securityElement.IncludeTimestamp = false;
            securityElement.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
            customBinding.Elements.Add(securityElement);

            // ORDER MATTERS: THIS HAS TO BE LAST!!! - HVT
            HttpsTransportBindingElement transportElement = new HttpsTransportBindingElement();
            transportElement.MaxBufferSize = int.MaxValue;
            transportElement.MaxReceivedMessageSize = int.MaxValue;
            customBinding.Elements.Add(transportElement);

            return customBinding;


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x