How to access Bearer authenticated service from Android
android, apache-httpclient-4.x, authentication, http-headers, oauth
Solution
This can be a tad complex but I've had success with it so I'll try to give you the benefit of my experience.
You have to provide a couple of items
- An implementation of org.apache.http.auth.Credentials
- An implementation of org.apache.http.auth.AuthSchemeFactory
Your Credentials implementation should be something similar to the following:
import java.security.Principal;
import org.apache.http.auth.BasicUserPrincipal;
import org.apache.http.auth.Credentials;
public class TokenCredentials implements Credentials {
private Principal userPrincipal;
public TokenCredentials(String token) {
this.userPrincipal = new BasicUserPrincipal(token);
}
@Override
public Principal getUserPrincipal() {
return userPrincipal;
}
@Override
public String getPassword() {
return null;
}
}
Then you need to implement the AuthSchemeFactory:
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AUTH;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthSchemeFactory;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.ContextAwareAuthScheme;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.message.BufferedHeader;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.CharArrayBuffer;
public class BearerAuthSchemeFactory implements AuthSchemeFactory {
@Override
public AuthScheme newInstance(HttpParams params) {
return new BearerAuthScheme();
}
private static class BearerAuthScheme implements ContextAwareAuthScheme {
private boolean complete = false;
@Override
public void processChallenge(Header header) throws MalformedChallengeException {
this.complete = true;
}
@Override
public Header authenticate(Credentials credentials, HttpRequest request) throws AuthenticationException {
return authenticate(credentials, request, null);
}
@Override
public Header authenticate(Credentials credentials, HttpRequest request, HttpContext httpContext)
throws AuthenticationException {
CharArrayBuffer buffer = new CharArrayBuffer(32);
buffer.append(AUTH.WWW_AUTH_RESP);
buffer.append(": Bearer ");
buffer.append(credentials.getUserPrincipal().getName());
return new BufferedHeader(buffer);
}
@Override
public String getSchemeName() {
return "Bearer";
}
@Override
public String getParameter(String name) {
return null;
}
@Override
public String getRealm() {
return null;
}
@Override
public boolean isConnectionBased() {
return false;
}
@Override
public boolean isComplete() {
return this.complete;
}
}
}
The next step is getting HttpClient to accept it as a valid scheme:
HttpContext httpContext = new BasicHttpContext();
AuthSchemeRegistry authSchemeRegistry = new AuthSchemeRegistry();
authSchemeRegistry.register("Bearer", new BearerAuthSchemeFactory());
httpContext.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry);
AuthScope sessionScope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM, "Bearer");
Credentials credentials = new TokenCredentials (token);
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(sessionScope, credentials);
httpContext.setAttribute(ClientContext.CREDS_PROVIDER, credentialsProvider);
I typically would create these on a per context basis and hang on to the context over a period of time.
Additional documentation can be found here. I would recommend staying away from "pre-emptive" authentication and let the famework do its job in handling a 401 challenge.
If you'd like to see what I'm talking about, turn up the logging in HTTP client so that you can trace the wire conversation - you'll see the initial request come back with a challenge, then the client will utilize the credentials provider to locate the appropriate credentials and send the request with the appropriate challenge response for the scheme we've defined.
Good luck!
Problem
I am going to use a service that is using Bearer authentication. I tried to fetch it from Android in vain. Here's my code. ``` String mytoken = "some token I am sure is right"; HttpClient witClient = new DefaultHttpClient(); Uri.Builder b = Uri.parse("www.somewebsite.com").buildUpon(); b.appendQueryParameter("q", "some query string"); String finalurl = b.build().toString(); HttpGet request = new HttpGet(new URI(finalurl)); request.setHeader("Authorization", "Bearer "+mytoken); HttpResponse response = witClient.execute(request); ``` The server would return me an error saying authentication is needed. Obviously the header is dropped somehow. ``` 11-22 21:50:42.180: W/DefaultRequestDirector(3408): Authentication error: Unable to respond to any of these challenges: {bearer=Www-Authenticate: Bearer realm="OAuth required"} ``` where's wrong