12

I created RESTful webservice (WCF) where I check credentials on each request. One of my clients is Android app and everything seems to be great on server side. I get request and if it's got proper header - I process it, etc..

Now I created client app that uses this service. This is how I do GET:

// Create the web request  
            var request = WebRequest.Create(Context.ServiceURL + uri) as HttpWebRequest;

            if (request != null)
            {
                request.ContentType = "application/json";

                // Add authentication to request  
                request.Credentials = new NetworkCredential(Context.UserName, Context.Password);

                // Get response  
                using (var response = request.GetResponse() as HttpWebResponse)
                {
                    // Get the response stream  
                    if (response != null)
                    {
                        var reader = new StreamReader(response.GetResponseStream());

                        // Console application output
                        var s = reader.ReadToEnd();

                        var serializer = new JavaScriptSerializer();
                        var returnValue = (T)serializer.Deserialize(s, typeof(T));

                        return returnValue;
                    }
                }
            }

So, this code get's my resource and deserializes it. As you see - I'm passing credentials in my call.

Then when debugging on server-side I noticed that I get 2 requests every time - one without authentication header and then server sends back response and second request comes bach with credentials. I think it's bad for my server - I'd rather don't make any roundtrips. How should I change client so it doesn't happen? See screenshot of Fiddler

First BAD request

Second GOOD request

EDIT:

This is JAVA code I use from Android - it doesn't do double-call:

MyHttpResponse response = new MyHttpResponse();
        HttpClient client = mMyApplication.getHttpClient();

        try
        {
            HttpGet request = new HttpGet(serviceURL + url);
            request.setHeader(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
            request.addHeader("Authorization", "Basic " + Preferences.getAuthorizationTicket(mContext));

            ResponseHandler<String> handler = new BasicResponseHandler();
            response.Body = client.execute(request, handler);
            response.Code = HttpURLConnection.HTTP_OK;
            response.Message = "OK";
        }
        catch (HttpResponseException e)
        {
            response.Code = e.getStatusCode();
            response.Message = e.getMessage();

            LogData.InsertError(mContext, e);
        }
katit
  • 17,375
  • 35
  • 128
  • 256
  • Have a look at the PreAuthenticate property of the request. [http://msdn.microsoft.com/fr-fr/library/system.net.webrequest.preauthenticate(v=vs.100).aspx](http://msdn.microsoft.com/fr-fr/library/system.net.webrequest.preauthenticate%28v=vs.100%29.aspx) – dlambert Oct 24 '12 at 08:55
  • Old question, but I solved it differently. I just put in header manually and no more issues. I don't use `request.Credentials` anymore – katit Oct 24 '12 at 17:31
  • As [Jon mentions in a similar questions](http://stackoverflow.com/a/20260035/25847), `PreAuthenticate` will still do some extra failed requests. – Mark Rogers May 16 '17 at 18:54

3 Answers3

12

The initial request doesn't ever specify the basic header for authentication. Additionally, since a realm is specified, you have to get that from the server. So you have to ask once: "hey, I need this stuff" and the server goes "who are you? the realm of answering is 'secure area'." (because realm means something here) Just because you added it here:

request.Credentials = new NetworkCredential(Context.UserName, Context.Password);

doesn't mean that it's going to be for sure attached everytime to the request.

Then you respond with the username/password (in this case you're doing BASIC so it's base64 encoded as name:password) and the server decodes it and says "ok, you're all clear, here's your data".

This is going to happen on a regular basis, and there's not a lot you can do about it. I would suggest that you also turn on HTTPS since the authentication is happening in plain text over the internet. (actually what you show seems to be over the intranet, but if you do go over the internet make it https).

Here's a link to Wikipedia that might help you further: http://en.wikipedia.org/wiki/Basic_access_authentication

jcolebrand
  • 15,889
  • 12
  • 75
  • 121
  • 1
    @jcolebrand - Yes, it is SSL and yes, it will be internet. I don't agree it have to be like that though. Like I mentioned, my Java client doesn't do that "Hey, I need that stuff". It goes with Auth header from first call and gets data back on first response. I'm sure I can re-write this .NET code so it does the same. My service stateless, auth will be checked on every call and I don't see a point in making extra roundtrip every time. – katit Jun 14 '11 at 05:34
  • I see your point, but I was just commenting that I don't think that you _can_ force .NET to do that using the request.Credentials. You'ld have to set the header manually, which I see you suggest below is what you did. However, that's (as you noticed) not intuitive. Additionally, you lose whatever benefit was gained from letting the framework handle all that should the authentication mechanism ever change but the actual authentication values remain the same. – jcolebrand Jun 14 '11 at 13:49
  • Good point, I didn't think about auth mechanism possibly changing. But it's not an issue in my case – katit Jun 14 '11 at 15:24
  • Agree, that makes perfect sense now. I don't see noticeable difference on my local machine but with chatty app on internet this kind of stuff may add up I would think. – katit Jun 14 '11 at 15:42
  • Indeed it would add up, you're correct. But you would have to scale upwards of 500 SIMULTANEOUS users (not casual users) before you would even BEGIN to notice. – jcolebrand Jun 14 '11 at 15:46
  • On server - yes. But on client side - if I make 15 requests and they all double in latency - it might be noticeable on internet. I guess I can somehow reuse connection.. – katit Jun 14 '11 at 15:52
  • Before you get too heavy into assumptions, profile it first. See what the actual latency is. I bet you won't even be able to reasonably measure it. – jcolebrand Jun 14 '11 at 15:53
  • I am having similar issue except that the service is written in java and running on tomcat. [please look at this issue](http://stackoverflow.com/questions/30617596/why-is-it-making-the-request-twice?noredirect=1#comment49301885_30617596) – sarfarazsajjad Jun 04 '15 at 03:11
  • I am trying to reset for the second request. But somehow its getting passed even after reset . http://stackoverflow.com/questions/38357792/set-network-credentials-via-config – Billa Jul 19 '16 at 15:21
10

Ok, I got it. I manually set HttpHeader instead of using request.Credentials

request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(Context.UserName + ":" + Context.Password)));

Now I see only single requests as expected..

katit
  • 17,375
  • 35
  • 128
  • 256
  • Don't you think that the builtin methods to do this instead of messing with headers directly? What Sergey proposed above seem clean – videoguy Mar 05 '21 at 04:46
1

As an option you can use PreAuthenticate property of HttpClientHandler. This would require a couple of lines more

            var client = new HttpClient(new HttpClientHandler
            {
                Credentials = yourCredentials,
                PreAuthenticate = true
            });

With using this approach, only the first request is sent without credentials, but all the rest requests are OK.