Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't authenticate towards SOAP service in ASP.NET Core

I'm trying to consume a SOAP web service from an ASP.NET Core Library project.

I have installed the Mictosoft WCF Web Service Reference Provider that has created the proxy classes for me, but I have a problem with the authentication.

My code currently looks like this:

var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);

var address = new EndpointAddress(@"https://myserviceaddress.asmx");
var client = new TestApiClient(binding, address);
client.ClientCredentials.UserName.UserName = "testusername";
client.ClientCredentials.UserName.Password = "testpassword";
var result = await client.GetDataAsync(params);

I have tried some different approaches, but I either get Unauthorized, or variants of this: The value 'TransportWithMessageCredential' is not supported in this context for the binding security property 'securityMode'..

I think I'm also supposed to set some properties on binding.Security.Message, but the only property that exist on binding.Security is Transport and Mode.

Anyone know how to set this up correctly?

Are there any other better ways of consuming the SOAP service from ASP.NET Core? I would love a dynamic way that don't have to create the proxy classes, but I can live with the proxies if I have to, but I need to be able to authenticate with a user and password, and I have to use https.

like image 690
Øyvind Bråthen Avatar asked Jan 29 '23 13:01

Øyvind Bråthen


1 Answers

After struggling many days with the same issue, because .Net core has a very limited support till date of this answer, I managed to solve it by building the whole envelope myself and using SimpleSoapClient.

I will try to go through all needed steps, step by step with a simple example. because each step has its own issues that needs to be solved.

Let us assume that your model is a Cat.

 public class Cat
    {
        public int Lives { get; set; }
    }

Your entire envelop model will look like:

public class Envelope
{
    public EnvelopeHeader Header { get; set; }
    public EnvelopeBody Body { get; set; }
}
public class EnvelopeHeader
{
    [XmlElementAttribute(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
    public Security Security { get; set; }
}


[XmlTypeAttribute(AnonymousType = true, Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
[XmlRootAttribute(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", IsNullable = false)]
public class Security
{
    public SecurityUsernameToken UsernameToken { get; set; }
}


[XmlTypeAttribute(AnonymousType = true, Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public  class SecurityUsernameToken
{
    public string Username { get; set; }
    public SecurityUsernameTokenPassword Password { get; set; }
    [XmlAttributeAttribute(Form = System.Xml.Schema.XmlSchemaForm.Qualified, Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
    public string Id { get; set; }
}


[XmlTypeAttribute(AnonymousType = true, Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public  class SecurityUsernameTokenPassword
{
    [XmlAttributeAttribute()]
    public string Type { get; set; }
    [XmlTextAttribute()]
    public string Value { get; set; }
}

public  class EnvelopeBody
{
    public Cat Cat{ get; set; }
}

P.S: do not forget to include XML namespaces where needed in your body.

Next step is serializing your Envelop model into XML string.

string xml;
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
using (MemoryStream ms = new MemoryStream())
  {
    using (XmlWriter writer = XmlWriter.Create(ms, settings))
     {
       XmlSerializerNamespaces names = new XmlSerializerNamespaces();
       names.Add("", "");//add your needed namespaces here
       XmlSerializer cs = new XmlSerializer(typeof(Envelope));
       var myEnv = new Envelope()
        {
         Header = new EnvelopeHeader()
          {
           Security = new Security()
            {
              UsernameToken = new SecurityUsernameToken()
               {
                Username = "",
                Password = new SecurityUsernameTokenPassword()
                {
                 Type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText",//update type to match your case
                 Value = ""
                }
               }
              }
             },
             Body = new EnvelopeBody()
             {
              Cat = new Cat() { Lives = 7 }
             }
            };
            cs.Serialize(writer, myEnv, names);
            ms.Flush();
            ms.Seek(0, SeekOrigin.Begin);
            StreamReader sr = new StreamReader(ms);
            xml = sr.ReadToEnd();
          }
     }

Finally send your XML envelop using:

  SoapEnvelope responseEnvelope = null;
        using (var client = SoapClient.Prepare().WithHandler(new DelegatingSoapHandler()
        {
            OnHttpRequestAsyncAction = async (z, x, y) =>
            {
                x.Request.Content = new StringContent(xml , Encoding.UTF8, "text/xml");
            }
        }))
        {
                responseEnvelope = client.SendAsync("url", "action",SoapEnvelope.Prepare()).Result;
        }

Please note that you need to update many options according to your case. My case was password of type "PasswordText" in the WSSE Security Header with WCF.

like image 159
Yahya Hussein Avatar answered Feb 02 '23 10:02

Yahya Hussein