Blogs:

Calling web services using basic authentication

Mark

March 23, 2005 / 32 Comments

I recently made a web services call into WebMethods using basic authentication.  This authentication meant that we needed to modify the WSDL generated classes to handle the authentication.

Here’s how it works.  I add a reference to the Web Service (Visual Studio generates the client code for calling the web service).  To this generated class I need to add the following method:

protected override System.Net.WebRequest GetWebRequest(Uri uri)
{
    HttpWebRequest request;
    request = (HttpWebRequest)base.GetWebRequest(uri);

    if (PreAuthenticate)
    {
        NetworkCredential networkCredentials =
        Credentials.GetCredential(uri, "Basic");

        if (networkCredentials != null)
        {
            byte[] credentialBuffer = new UTF8Encoding().GetBytes(
            networkCredentials.UserName + ":" +
            networkCredentials.Password);
            request.Headers["Authorization"] =
            "Basic " + Convert.ToBase64String(credentialBuffer);
        }
        else
        {
            throw new ApplicationException("No network credentials");
        }
    }
    return request;
}

This overrides the GetWebRequest() method of the System.Web.Services.Protocols.SoapHttpClientProtocol class that the web service client code derived from.

With Visual Studio 2005 the generated code code is a C# 2.0 partial classes.  As a result, regenerating the web services client code does not over-write the additional method.  To enable this, add a class file to your project and give it the same namespace and name as the generated System.Web.Services.Protocols.SoapHttpClientProtocol derived class.  The key is to use the partial modifier on the class header so that the GetWebRequest() method is added to the generated class.  (partial class Michaelis.MockService{…})

Regardless of using Visual Studio.NET 2005 or earlier, the client code requires that the network credentials are set and the PreAuthenticate property is assigned true.  Here is a sample client call:

Michaelis.MockService service = new Michaelis.MockService();

// Create the network credentials and assign
// them to the service credentials
NetworkCredential netCredential = new NetworkCredential("Inigo.Montoya", "Ykmfptd");
Uri uri = new Uri(service.Url);
ICredentials credentials = netCredential.GetCredential(uri, "Basic");
service.Credentials = credentials;

// Be sure to set PreAuthenticate to true or else
// authentication will not be sent.
service.PreAuthenticate = true;

// Make the web service call.
service.Method();

UPDATE – 4/14/2005

Comments on the post raised the question, “Why cant you just say request.Credentials = new NetworkCredential(username,password).”

The reason relates to interoperating with WebMethods specifically.  When just setting Credentials, the HTTP header looks like this:

POST /soap/rpc HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50113.0)
Content-Type: text/xml; charset=utf-8
SOAPAction: “”
Host: <servername>:<port>
Content-Length: 779
Expect: 100-continue
Accept-Encoding: gzip

Notice, there is no Authentication item even though PreAuthenticate is set to true.

The reply back from WebMethods is as follows:

HTTP/1.0 500 Internal Server Error
Set-Cookie: ssnid=11747k5Rwchr3vW0s23vcaCP1wCA2NDc=555590; path=/;
Content-Type: text/xml;charset=utf-8
Connection: Keep-Alive
Content-Length: 849

The problem is that .NET is expecting a challenge response from WebMethods, specifically a 401 error of “Invalid credentials.”  However, if the client’s credentials are not specified (there is not Authentication part to the header) then WebMethods returns an HTTP 500 status code (Internal Server Error) indicating that the request could not be fulfilled.

To fix the problem you can either change the .NET client or else the WebMethods server.  In my original posting, I demonstrated how to control the .NET client.  The result was an HTTP header that includes the Authentication portion as shown below:

POST /soap/rpc HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50113.0)
Authorization: BasicbGRwcm86bGRwcm8=
Content-Type: text/xml; charset=utf-8
SOAPAction: “”
Host: spo-wm-py-srvr:5555
Content-Length: 779
Expect: 100-continue
Accept-Encoding: gzip

However, it is also possible to change the WebMethods side (assuming you control that side) by creating an access controlled SOAP processor that checks the credentials for each client request against a specified ACL and returns an HTTP 401 status code even if there are no credentials passed.

By the way, the tool I use for tracing HTTP is YATT.

AUTHOR: CATEGORY: .Net

32 Responses to “Calling web services using basic authentication”

  1. Maheshwar says:

    I am surprised why you had to manually send a basic authentication header with the request.

    Why cant you just say
    request.Credentials = new NetworkCredential(username,password)

    or if you want to use your windows logged on user identity

    request.Credentials = CredentialCache.DefaultCredentials

  2. Milo says:

    What happens if the webservice is secure? I’ve never used 2005, so I’m assuming this is simlar to httpWebRequest? When I have had authenticate I have always used the method above. (Maheshwar’s)

    Good stuff Mark.

  3. Sergi says:

    Just wanted to say thanks!

    Me and my team have been looking into it for over a week now and it was royally driving us nuts!

    Bleedin’ ‘ell, what a relief…

    Thanks a million!

  4. Brian Delaney says:

    Thank you very much. This worked great.

  5. Justin Ramel says:

    Great post saved me lots of time thanks!

    To get the code to work I did have to make a slight change which was to simply add a space ("Basic ") to the following line:

    request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(credentialBuffer);

  6. Kai says:

    Many, many, many thanks. It took me the whole day to finally find the solution to this problem on your site. As I said: Many thanks :-)

    A little suggestion: I wouldn’t change the autogenerated code, instead I subclassed the generated class and added the code there. So it is safe to update the webservice at a later point.

  7. Nick Ohrn says:

    I just wanted to say thank you so much for this method. It was literally driving me bonkers. You have been so much help, and I sincerely appreciate it.

  8. James Hogans says:

    I’ve tried the code and it works but only for the first call. If I make a second call, I get an exception, "The underlying connection was closed: An unexpected error occurred on a send". You have any ideas? THanks.

  9. Walo Hürzeler says:

    Thank you very much!
    I spend days to investigate that problem!
    You should tell this the guys at webmethods, because the forum is full of threads with that problem!

    Thank you!!!

  10. Eliseu Martinho says:

    Thanks for the post! My collegue and I were going nuts for almost over 2 weeks trying to figure what was the problem with authentication!

    Thanks again,

    eliseu martinho

  11. nate says:

    thanks for the post helped out a lot.

  12. Tommy says:

    Another poor soul that’s been saved by your blog post, Mark! However, I had to insert a space between "Basic" and the BASE64 encoded credentials string, but maybe that’s just me.

    Anyway, thanks a million! :D

  13. David Homer says:

    Helpful post!

    Is there any way to force BASIC authentication from the client app? For example if IIS is configured for both BASIC and INTEGRATED authentication and the Client app can authenticated using INTEGRATED however you want to explicitly override this using BASIC authentication how can this be achived?

    Thanks,

    Dave

  14. Dan Kristensen says:

    Great post Mark, but like Tommy I had to insert a space between "Basic" and the BASE64 encoded credentials. Thanks! :o)

  15. Mehul Bhuva says:

    Thanks Mark for this wonderfull post, it solved my problem of Basic authentication using Web-services…

  16. Rich Jardine says:

    Mark,
    I’ll add my thanks to all the others. I was having trouble connecting with an HP Service Manager Web Service ( Apache ) from a .NET client. The HPSM process dies on the UNIX box without the preauthentiation header. I wasted a lot of time until I found this post on your blog. You are steely eyed coder man. Many thanks!!!

    Best Regards,
    Rich

    p.s.
    I only had to modify one line of your function:
    from
    "Basic" + Convert.ToBase64String(credentialBuffer);
    to
    "Basic" + "\t" + Convert.ToBase64String(credentialBuffer);

    • HPSM says:

      Hi,
      Even i want to connect to HPSM Incident Management Web Service to Create Incident.

      I am getting error message:
      SoapHeaderException: Bad auth String (could not parse username/password): String index out of range: -1

      please help

  17. Ahmet ARDAL says:

    Mark,
    Thanks in advance…

  18. syam says:

    Hi mark, this article is really helpful. I want to configure client to send credentials for each request. You said "In my original posting, I demonstrated how to control the .NET client." Can you please share me the link for configuring client? Thanks in advance.

  19. Rick Schott says:

    Very helpful, solved my issue.

  20. debajyoti says:

    hi, i am using remedy itsm 7.6.03
    while consuming a web service it shows a error the request failed with HTTP status 505 version not supported.
    plz help me to solve this.

  21. Srinath says:

    Your write up is very straight. Thanks for the post.

  22. uday says:

    Hi,

    I have implemented the code suggested here but it is not working, can someone please suggest if I need to change anything to make it work.

  23. Oregon Mike says:

    This is FANTASIC!!! I wish I would have read Rich Jardine’s comments first. I got it working by using “Basic ” + Convert.ToBase64String(credentialBuffer);

  24. Oscar says:

    2 days fighting with the problem of sending the auth header.. until I have found this post this morning.

    Thanks a lot!

    <3

  25. Nik says:

    Hi,

    I am getting error message:
    SoapHeaderException: Bad auth String (could not parse username/password): String index out of range: -1

    please help

  26. Urmy says:

    Bad auth String (could not parse username/password): String index out of range: -1

  27. Franz says:

    Thousand thanks Mark for this helpful post. Even it is 8 years old, it still helps.

  28. Shimon Cohen says:

    hi,
    This is great.
    can you please let me know how to add ws-Security Header?

    (
    #
    #
    #
    #THubUser
    #Password1234
    #
    #
    #
    )

  29. Krish says:

    Thanks a lot Mark for this wonderful post. first time the code didn’t work. But after going through all comments I figured it out that a space is required after the Basic word. Once I made that change it worked. Will it be possible for you to update the code sample to include that extra space required. This will help others also and saves time who just copy and paste your code.

    Thanks again.

  30. Bharat Garg says:

    Can you please let me know how can i implement ws addressing in it.

Leave a Reply


Contact Us
Email: info@IntelliTect.com
Phone: (509) 315-3400




Testimonials

Mark and his IntelliTect team performed a number of tasks for Brahma including an architectural review of our solution, setting up and configuring Team Foundation Server including continuous integration builds and providing guidance and training on development processes and unit testing.
- Brahma Holding, La Jolla, CA
CONTACT US: (509) 315-3400 | Info@IntelliTect.com