24

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.

Geek
  • 8,280
  • 17
  • 73
  • 137
  • String authString = "username:password"; this "username:password" should be equal to their strings i mean in Website developers model. for eg:they may used Loginid:password like that. – manivannan Jun 19 '13 at 13:39
  • @AsafK Reason was that HttpUrlConnection raise an exception for status code 400 and above. We need to handle this exception. In my case, I ended up using HttpClient instead of HttpUrlConnetion on account of its handling of all status codes. – Geek Nov 26 '13 at 15:02
  • @Akash The 401 i was getting was because this is what im sending back in case the credentials are wrong. I was just wondering about the error message ("No authentication challenges found."), which seems too generic to me. anyway, according to what you're saying an IOException will occur in that case which i need to handle. Thanks. – AsafK Nov 26 '13 at 15:50
  • were you able to fix it? – Blackbelt Mar 06 '14 at 17:23
  • @blackbelt I don't exactly remember what the cause was. You can check if you are passing correct credentials. Or try usign `HttpClient` instead of `HttpUrlConnection`. I switched to that. – Geek Mar 06 '14 at 18:03

5 Answers5

51

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. Its 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
Patrick
  • 33,984
  • 10
  • 106
  • 126
1

What version of Android are you testing on?

I had difficulties with the Android authenticator during some development work on Gingerbread (I don't know if it behaves differently on later versions of Android). I used Fiddler2 to examine the HTTP traffic between my app and the server, discovering that the authenticator did not send out the authentication string for every HTTP request. I needed it to.

Instead, I resorted to this:

urlConnection.setRequestProperty("Authorization", "Basic " + Base64.encodeToString("userid:pwd".getBytes(), Base64.NO_WRAP ));

It's a cut-and-paste from my code. Note that urlConnection is an HttpURLConnection object.

UpLate
  • 1,266
  • 12
  • 17
  • I have tried this. But did not work. Even I have tried using different base64 flags like `URL_SAFE`, `NO_WRAP`, `DEFAULT`. I also tried "`URL_SAFE | NO_WRAP`". – Geek Jun 15 '13 at 11:27
  • I checked that Fiddler2 is for windows. Is there such for MAC? – Geek Jun 15 '13 at 11:31
  • 1) in my code, I removed the authenticator (I didn't try combinations of it and setRequestProperty) 2) for the setRequestProperty above, NO_WRAP was definitely necessary since removing it caused a failure 3) Sorry. I'm developing on Windows. I don't know what Mac tools exist. – UpLate Jun 15 '13 at 19:46
  • Does you iOS application use cookies? In my Android app I had to specifically turn this on. – UpLate Jun 15 '13 at 20:50
  • No, it does not. Is there any relation of my question with this? – Geek Jun 16 '13 at 06:06
  • No. I was exploring possibilities. Cookies was the only thing I could think of but you've now indicated that your REST API doesn't utilize them (since your iOS app isn't). I'm not familiar with the exact protocol for connecting with a REST API so I cannot pick apart the rest of your code. – UpLate Jun 16 '13 at 10:18
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31821/discussion-between-akash-and-uplate) – Geek Jun 16 '13 at 10:22
1

I had the same issue on devices running pre-KitKat Android, but I was using the Volley library so the client side fix provided by @for3st didn't work for me, I had to adjust it for Volley, here it is, hope it helps someone struggling with this problem:

HurlStack hurlStack = new HurlStack() {
            @Override
            public HttpResponse performRequest(final Request<?> request, final Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
                try {
                    return super.performRequest(request, additionalHeaders);
                } catch (IOException e) {
                    return new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 401, e.getMessage());
                }
            }
        };

Volley.newRequestQueue(context.getApplicationContext(), hurlStack);

This way a 401 error is returned and your retry policy can do it's job (e.g. request token... etc...). Although IOException could be caused by some other problem, other then a 401, so you could opt for parsing the exception message for Authorization keyword, and return a different response code for others.

Leo K
  • 808
  • 1
  • 9
  • 24
1

Had the same issue on some old devices (Huawei Y330-U11 for instance). Correct way to fix it is to fix it server side as mentioned in the most popular answer.

However, it's really disappointing that the issue occurs only on some devices. And I believe it happens due to different implementations of "UrlConnection". Different Android versions - different "UrlConnection" implementations.

So, you might want to fix it by using the same "UrlConnection" everywhere. Try to use okhttp and okhttp-urlconnection.

Here is the way to add those libs to your gradle build:

compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.5.0'

It solved the problem for me on those outdated devices. (I had to use OkClient for Retrofit RestAdapter)

P.S. Latest Androids at time of writing use old version of OKHTTP library internally as "UrlConnection" implemetation (with updated package names), so it seems to be quite solid thing

GregoryK
  • 3,011
  • 1
  • 27
  • 26
0

you can use something like this

catch (IOException ex) {
        if(ex.getMessage().toString().toLowerCase().equals(("No authentication challenges found").toLowerCase()))
// re-generate the authentication Token
             }
Wowo Ot
  • 1,362
  • 13
  • 21