java.io.IOException : No authentication challenges found

Geek picture Geek · Jun 15, 2013 · Viewed 32.1k times · Source

I am newbie to android and this is my first project on android. I am struggling with "authentication" problem for more than a day. I tried several options but none of them worked.

Basically, I want to call a REST API and get response. I am sure that there is no problem in API as I use the same one in another iOS application.

I pass authorization header but still authentication no found message is shown. I found few question on stackoverflow related to this, but some of them did not work and some does not make sense to me.

I get status code 401. I know this means either no authentication passed or if passed, then they are wrong. Here, I am sure my passed ones are correct.

Below is my code :

try {
    url = new URL(baseUrl);
}
catch (MalformedURLException me) {
     Log.e(TAG, "URL could not be parsed. URL : " + baseUrl + ". Line : " + getLineNumber(), me);
     me.printStackTrace();
}

try {
    urlConnection = (HttpURLConnection) url.openConnection();
    urlConnection.setRequestMethod(method); 
    urlConnection.setConnectTimeout(TIMEOUT * 1000);
    urlConnection.setChunkedStreamingMode(0);

    // Set HTTP headers                 
    String authString = "username:password";
    String base64Auth = Base64.encodeToString(authString.getBytes(), Base64.DEFAULT);
    urlConnection.setRequestProperty("Authorization", "Basic " + base64Auth);
    urlConnection.setRequestProperty("Accept", "application/json");
    urlConnection.setRequestProperty("Content-type", "application/json");

    if (method.equals("POST") || method.equals("PUT")) {
        // Set to true when posting data
        urlConnection.setDoOutput(true);

        // Write data to post to connection output stream
        OutputStream out = urlConnection.getOutputStream();
        out.write(postParameters.getBytes("UTF-8"));
    }

    try {
        // Get response
        in = new BufferedInputStream(urlConnection.getInputStream());
    }
    catch (IOException e) {
        Log.e(TAG, "Exception in getting connection input stream. in : " + in);
                    e.printStackTrace();
    }

    // Read the input stream that has response
    statusCode = urlConnection.getResponseCode();
    Log.d(TAG, "Status code : " + statusCode);
}
catch (ProtocolException pe) {
    pe.printStackTrace();
}
catch (IllegalStateException ie) {
    ie.printStackTrace();
}
catch (IOException e) {
    e.printStackTrace();
}   
finally {
    urlConnection.disconnect();
}


Look at screenshot of logcat :

logcat

Any help would be appreciated. Thank you.

Answer

Patrick Favre picture Patrick Favre · Feb 3, 2014

This error happens because the server sends a 401 (Unauthorized) but does not give a WWW-Authenticate header which is a hint to the client what to do next. The WWW-Authenticate header tells the client, which kind of authentication is needed (either Basic or Digest). This is probably not very useful in headless http clients, but that's how the HTTP 1.1 RFC is defined. The error occurs because the lib tries to parse the WWW-Authenticate header but can't.

From the RFC:

(...)The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource.(...)

Possible solutions if you can change the server:

  • Add a fake "WWW-Authenticate" header like: WWW-Authenticate: Basic realm="fake". This is a mere workaround not a solution, but it should work and the http client is satisfied (see here a discussion of what you can put in the header). But beware that some http clients may automatically retry the request resulting in multiple requests (e.g. increments the wrong login count too often). This was observed with the iOS http client.
  • As proposed by loudvchar in this blog to avoid automatic reactions to the challenge like a pop-up login form in a browser, you can use a non-standard authentication method like so: WWW-Authenticate: xBasic realm="fake". The important point is that the realm has to be included.
  • Use HTTP status code 403 instead of 401. It's semantic is not the same and usually when working with login 401 is a correct response (see here for a detailed discussion) but the safer solution in terms of compatibility.

Possible solutions if you can't change the server:

  • As @ErikZ wrote in his post you could use a try&catch

    HttpURLConnection connection = ...;
    try {
        // Will throw IOException if server responds with 401.
        connection.getResponseCode(); 
    } catch (IOException e) {
        // Will return 401, because now connection has the correct internal state.
        int responsecode = connection.getResponseCode(); 
    }
    
  • Use different http client like OkHttp