78

I'm using the OkHttp library for a new project and am impressed with its ease of use. I now have a need to use Basic Authentication. Unfortunately, there is a dearth of working sample code. I'm seeking an example of how to pass username / password credentials to the OkAuthenticator when an HTTP 401 header is encountered. I viewed this answer:

Retrofit POST request w/ Basic HTTP Authentication: "Cannot retry streamed HTTP body"

but it didn't get me too far. The samples on the OkHttp github repo didn't feature an authentication-based sample either. Does anyone have a gist or other code sample to get me pointed in the right direction? Thanks for your assistance!

Community
  • 1
  • 1
Kerr
  • 993
  • 1
  • 7
  • 11

13 Answers13

117

Update Code for okhttp3:

import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;

public class NetworkUtil {

private final OkHttpClient.Builder client;

{
    client = new OkHttpClient.Builder();
    client.authenticator(new Authenticator() {
        @Override
        public Request authenticate(Route route, Response response) throws IOException {
            if (responseCount(response) >= 3) {
                return null; // If we've failed 3 times, give up. - in real life, never give up!!
            }
            String credential = Credentials.basic("name", "password");
            return response.request().newBuilder().header("Authorization", credential).build();
        }
    });
    client.connectTimeout(10, TimeUnit.SECONDS);
    client.writeTimeout(10, TimeUnit.SECONDS);
    client.readTimeout(30, TimeUnit.SECONDS);
}

private int responseCount(Response response) {
    int result = 1;
    while ((response = response.priorResponse()) != null) {
        result++;
    }
    return result;
}

}
nuss
  • 1,280
  • 2
  • 9
  • 12
68

As pointed out by @agamov:

The aforementioned solution has one drawback: httpClient adds authorization headers only after receiving 401 response

@agamov proposed then to "manually" add authentication headers to each request, but there is a better solution: use an Interceptor:

import java.io.IOException;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

public class BasicAuthInterceptor implements Interceptor {

    private String credentials;

    public BasicAuthInterceptor(String user, String password) {
        this.credentials = Credentials.basic(user, password);
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Request authenticatedRequest = request.newBuilder()
                    .header("Authorization", credentials).build();
        return chain.proceed(authenticatedRequest);
    }

}

Then, simply add the interceptor to an OkHttp client that you will be using to make all your authenticated requests:

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new BasicAuthInterceptor(username, password))
    .build();
Alphaaa
  • 4,206
  • 8
  • 34
  • 43
  • 2
    This works perfectly. Exactly what I needed to avoid multiple 401 calls. My API requires all calls to be authenticated. – Hackmodford Aug 10 '16 at 19:44
  • Is there a kotlin version of this Interceptor? @Alphaaa – stebak Mar 21 '18 at 08:46
  • @KBJ This interceptor works with OkHttp, which is a Java library. HTTP libraries typically allow to work with interceptors, so if you find one for Kotlin, you can implement something similar to this :) – Alphaaa Mar 21 '18 at 18:28
  • In my opinion, we can use Interceptor interface if all endpoints require authentication or we have a finite list of endpoints with this requirement. When we don't know which exactly endpoints require authentication we can use Authenticator interface. When we need we can mix this 2 approaches to avoid double requests (401 -> 200) and avoid adding Authorization header in places where we don't need it (security issue). – ultraon Dec 17 '18 at 23:32
  • This is not the most efficient way to do this. The reason is, the interceptor code will be executed every time we make request to the server. So, this way we will be creating a `request.newBuilder()` eveytime. Where as, authenticator should be added to client only ones. – Shriharsh Pathak Dec 14 '21 at 08:17
56

Here's the updated code:

client.setAuthenticator(new Authenticator() {
  @Override
  public Request authenticate(Proxy proxy, Response response) throws IOException {
    String credential = Credentials.basic("scott", "tiger");
    return response.request().newBuilder().header("Authorization", credential).build();
  }

  @Override
  public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
    return null;
  }
})
David Gageot
  • 2,496
  • 1
  • 22
  • 13
41

Try using OkAuthenticator:

client.setAuthenticator(new OkAuthenticator() {
  @Override public Credential authenticate(
      Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
    return Credential.basic("scott", "tiger");
  }

  @Override public Credential authenticateProxy(
      Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
    return null;
  }
});

UPDATE:

Renamed to Authenticator

Jemshit
  • 9,501
  • 5
  • 69
  • 106
Jesse Wilson
  • 39,078
  • 8
  • 121
  • 128
38

The aforementioned solution has one drawback: httpClient adds authorization headers only after receiving 401 response. Here's how my communication with api-server looked like: enter image description here

If you need to use basic-auth for every request, better add your auth-headers to each request or use a wrapper method like this:

private Request addBasicAuthHeaders(Request request) {
    final String login = "your_login";
    final String password = "p@s$w0rd";
    String credential = Credentials.basic(login, password);
    return request.newBuilder().header("Authorization", credential).build();
}
agamov
  • 4,407
  • 1
  • 27
  • 31
  • Good catch. I wrote an improved answer that builds from yours: http://stackoverflow.com/a/36056355/2091700 – Alphaaa Mar 17 '16 at 09:27
10

Okhttp3 with base 64 auth

String endpoint = "https://www.example.com/m/auth/"
String username = "user123";
String password = "12345";
String credentials = username + ":" + password;

final String basic =
        "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
Request request = new Request.Builder()
        .url(endpoint)
        .header("Authorization", basic)
        .build();


OkHttpClient client = SomeUtilFactoryClass.buildOkhttpClient();
client.newCall(request).enqueue(new Callback() {
...
s-hunter
  • 24,172
  • 16
  • 88
  • 130
  • 5
    `okhttp3.Credentials.basic(user, pass)` does this, so I think it should be preferred (just because it results in less code to mantain). – francesco foresti Mar 20 '17 at 09:08
  • @francescoforesti According to the doco, that will result in set of challenge/response requests, this is better if you don't want to do that. – Shorn Jul 18 '17 at 04:30
  • 4
    @Shorn `okhttp3.Credentials.basic(user, pass)` does not make any requests or changes any behaviour, it simply converts the username and password into a basic auth string. – FrederikNS Aug 22 '17 at 08:15
8

In my case it only worked when I integrated authorization into the header (OkHttp Version 4.0.1):

Request request = new Request.Builder()
    .url("www.url.com/api")
    .addHeader("Authorization", Credentials.basic("username", "password"))
    .build();

Request response = client.newCall(request).execute();
Codev
  • 1,040
  • 2
  • 14
  • 31
6

Someone asked for a Kotlin version of the interceptor. Here is what I came up with and it works great:

        val client = OkHttpClient().newBuilder().addInterceptor { chain ->
        val originalRequest = chain.request()

        val builder = originalRequest.newBuilder()
                .header("Authorization", Credentials.basic("ausername", "apassword"))
        val newRequest = builder.build()
        chain.proceed(newRequest)
    }.build()
5

In OkHttp3, you set the authorization on the OkHttpClient itself by adding the authenticator() method. After your original call comes back with the 401 response, the authenticator() adds the Authorization header

 new OkHttpClient.Builder()
        .connectTimeout(10000, TimeUnit.MILLISECONDS)
        .readTimeout(10000, TimeUnit.MILLISECONDS)
        .authenticator(new Authenticator() {
           @Nullable
           @Override
           public Request authenticate(@NonNull Route route, @NonNull Response response) {
             if (response.request().header(HttpHeaders.AUTHORIZATION) != null)
               return null;  //if you've tried to authorize and failed, give up

             String credential = Credentials.basic("username", "pass");
             return response.request().newBuilder().header(HttpHeaders.AUTHORIZATION, credential).build();
          }
        })
        .build();

Although it's more secure, if you don't want to spam the server with all the 401 requests in the first place, you can use something called preauthentication, where you send the Authorization header to begin with on your requests

String credentials = Credentials.basic("username", "password");
Request httpRequest = new Request.Builder()
                 .url("some/url")
                 .header("content-type", "application/json") 
                 .header(HttpHeaders.AUTHORIZATION, credentials)
                 .build();
sea cat
  • 251
  • 5
  • 8
3

I noticed on Android with some server APIs like django you should add a word in token

Request request = new Request.Builder()
    .url(theUrl)
    .header("Authorization", "Token 6utt8gglitylhylhlfkghriyiuy4fv76876d68")
    .build();

, where that problematic word is that "Token ". Overall you should carefully see rules of those specific server APIs about how to compose requests.

CodeToLife
  • 3,672
  • 2
  • 41
  • 29
  • Depends on the server and of course the token type, for example a Bearer token value should be something like `Bearer 6utt8gglitylhylhlfkghriyiuy4fv76876d68` – Ahmet Gokdayi Dec 07 '19 at 08:21
2

All answers are good but no one said, that for some requests content-type is required, you should add a content-type to your request like this:

Request request = new Request.Builder()
        .url(url)
        .addHeader("content-type", "application/json") 
        .post(body)
        .build();

If you don't add it, you will get Unauthorized message and you will waste a lot of time to fix it.

Nosov Pavel
  • 1,571
  • 1
  • 18
  • 34
0

This is a snippet for OkHttp Client:

  OkHttpClient client = new OkHttpClient.Builder()
               .authenticator(new Authenticator() {
              @Override public Request authenticate(Route route, Response 
   response) throws IOException {
                   if (response.request().header("Authorization") != null) {
                      return null; // Give up, we've already attempted to 
   authenticate.
                   }

                  System.out.println("Authenticating for response: " + response);
                  System.out.println("Challenges: " + response.challenges());
                   String credential = Credentials.basic(username, password);
                   return response.request().newBuilder()
                           .header("Authorization", credential)
                           .build();
               }
           }) .build(); 

Do a request now. Basic auth will go as client already has that.

    Request request = new Request.Builder().url(JIRAURI+"/issue/"+key).build();
                client.newCall(request).enqueue(new Callback() {
                    @Override
                   public void onFailure(Call call, IOException e) {
                       System.out.println("onFailure: "+e.toString());
                    }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    System.out.println( "onResponse: "+response.body().string());

                }
            });
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Vayuj Rajan
  • 85
  • 1
  • 2
0

You can try this code for a no frills approach.

    String credentials = username + ":" + password;

    final String basic = "Basic " + 
    java.util.Base64.getEncoder().encodeToString(credentials.getBytes());

    Request request = new Request.Builder().header("Authorization", 
    basic).url(connectString).post(body)
sqrepants
  • 996
  • 2
  • 10
  • 22