Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to respond to HTTP authentication challenges in Android

In my app I make HTTP calls to a web service over HTTPS. My desire is to design my client in such a way that it only asks the user for their username and password when there is an authentication challenge. I want to be reactive, not preemptive. Due to the nature of my app, there are certain cases where it would not need to authenticate.

In iOS, this process is made very easy via the NSURLConnectionDelegateProtocol, which is implemented in the iOS version of my app. A connection is made using this class, and only when a challenge is presented does the class asks its delegate for authentication credentials.

How can this be done in Android? Using the AuthenticationHandler? Or perhaps the Authenticator? If so, could can example or tutorial be provided?

Edit 1:

Using the Authenticator class (see my answer below) I am now able to respond to authentication challenges, but currently only with hard-coded credentials. I want to prompt the user for a username and password. According to the Android documentation, within the getPasswordAuthentication method you "usually prompt the user for required input".

How is this possible? Without blocking the UI thread, which I know cannot/should-not be done, how can you display a dialog, wait for user input, then retrieve the entered credentials and return them, all within that method?

Edit 2:

While there are little or no examples on the use of the Authenticator class, what I have been able to find suggests that prompting the user for input is impossible from within a single method, like getPasswordAuthentication. Might have to revert to the Apache Http client...

Final Edit:

I am still using HttpURLConnection, but doing something along the lines of what Edward suggested below. There doesn't seem to be any configurable class for handling authentication challenges and prompting the user for input, so I am doing this manually.

like image 498
Groppe Avatar asked Nov 28 '12 18:11

Groppe


2 Answers

Send your request without authentication headers. If authentication is required, the connection will fail with a 401 error. Let that failure bubble back up to whatever code made the initial request. That code should then show an authentication dialog to the user. Remember the username/password that the user provides, and retry the failed request with the necessary authentication header added to the request.

Remember the username/password for the rest of the session so you don't have to keep asking the user.

I'm sorry that I don't have any sample code; I've implemented this in java.net, but that won't do you any good if you're using apache.

This thread may help you: Http Basic Authentication in Java using HttpClient?

Edit:

I have implemented this in java.net, but it was work I did for hire and I don't have access to the source code. One problem is that java.net doesn't let you set authentication on a per-connection basis, but instead has you implement a single global authenticator. Since my app only communicated with one specific web site, that wasn't an issue.

What I did was create a subclass of Authenticator that caches the username/password data from the user. The initial value of the cache was empty. There is also an "attempt counter". When the authenticator is called and there is no cached user/password info, the user is prompted via a dialog. If the cached values are present, they are returned without bothering the user.

If there are subsequent calls to the authenticator, it's assumed that the user/password info was incorrect, and the user is prompted again. If the counter reaches ten, then the authenticator returns null (failure).

The counter is reset prior to each new internet connection.

A smarter authenticator would pay attention to the host and/or realm (prompt string) and cache user/password accordingly.

I'm not sure any of this will be useful to you in an Android environment, since popping up a dialog might not be an available option to your authenticator. But I could be wrong about that; it depends on your application.

In an Android environment, you'll want to do your http requests from a thread and then signal the activity when the data has arrived. If your thread detects a 401 error, it would signal the activity to that effect, and then the activity could pop up a dialog and re-launch the thread. The thread would then register a trivial authenticator that simply returned the user/password information. Alternatively, the thread could manually build an Authorization header, although that's more work.

like image 174
Edward Falk Avatar answered Oct 23 '22 20:10

Edward Falk


For those who don't know, there are two major Http client API's available for use in Android:

  • Apache HTTP Client
  • java.net HttpURLConnection

A recent overview of both of these is given in this Android Developers Blog post.

I've chosen to use the java.net implementation, because, quoting the blog: "New applications should use HttpURLConnection; it is where we will be spending our energy going forward." Also, HttpURLConnection will connect using SSL automatically if the Url provided is Https. As I stated in the original post, I was looking for an interface within the API that would allow me to automatically handle authentication challenges, if necessary. I've found that the Authenticator class does what I want.

Here is the code that I have so far, and it works:

URL Url = new URL(url);

Authenticator.setDefault(new Authenticator () {
    protected PasswordAuthentication getPasswordAuthentication() {
        return (new PasswordAuthentication("username", "password".toCharArray()));
    }
});

connection = (HttpURLConnection) Url.openConnection();
connection.setRequestProperty("Authorization", "Basic");

content = new BufferedInputStream(connection.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(content));
String line;
while ((line = reader.readLine()) != null) {
    stringBuilder.append(line);
}

The key (for me) to getting the Authenticator's method to actually be called, was this line:

connection.setRequestProperty("Authorization", "Basic");

Otherwise, the request would actually return a response of 200 (OK), and redirect me to a login page.

Next Step: From the getPasswordAuthentication method above, prompt the user for a username and password, and somehow return it. If you can help with this, post an answer!

like image 28
Groppe Avatar answered Oct 23 '22 19:10

Groppe