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!
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;
}
}
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();
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;
}
})
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
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:
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();
}
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() {
...
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();
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()
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();
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.
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.
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());
}
});
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)
Related
This question basically wants to know how to use stored cookies for subsequent request. The long text below is just for example. Basic question is how to use the same cookie for all requests on a particular website.
Basically I am trying to reach the login page of a website within the android app. The website works the following way.
There are 3 urls to consider.
1 -> "http://www.example.com/timeoutPage"
2 -> "http://www.example.com/mainPage"
3 -> "http://www.example.com/loginPage"
The two main points to consider are
(1) If we directly go to the 3rd url (loginPage), it redirects to the 1st url(timeoutPage). The timeoutPage has a button to go to the mainPage.
(2) If we go to the 2nd url (mainPage), it gives us a cookie. Now, after getting the cookie, when we visit the 3rd url (loginPage), we are able to access it. The loginPage has a captcha so it's essential to visit it(loginPage) in order to login.
Without the cookie, which is given at visiting 2nd url(mainPage), we cannot directly access 3rd url(loginPage).
so what i am doing is to make a ClearableCookieJar and attach it to OkHttpClient.
OkHttpClient client;
ClearableCookieJar cookieJar;
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(this));
client = new OkHttpClient.Builder()
.cookieJar(cookieJar)
.build();
Request request = new Request.Builder()
.url("http://www.example.com/mainPage")
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
textView.setText("Failed to get response");
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){
final String myResponse = response.body().string();
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText(myResponse);
}
});
}
}
});
Everthing is fine till here as i am able to print the html of mainPage in my textview.
Problem starts here when i make another request for the loginPage.
request = new Request.Builder()
.url("http://www.example.com/loginPage")
.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0")
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
textView.setText("Failed to get response");
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){
final String myResponse2 = response.body().string();
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText(myResponse2);
}
});
}
}
});
Here i again make a request to the loginPage but i reach the timeoutPage. How should i make the request for the loginPage url so that my request sends the cookie which i stored in the cookieJar while making a request to mainPage. One way i thought of was
request = new Request.Builder()
.url("http://www.example.com/loginPage")
.addHeader("Cookie", cookieStringGivenAtMainPage)
.build();
But i don't know how to access that cookieStringGivenAtMainPage. How should i reach that loginPage?
I print the html of the response to see if i reached the correct page.
It seems the answer you want to know is the singleton pattern.
Please refer to below sample code. I got it from kakao developer site about 3 years ago.
public class NetworkManager {
private static NetworkManager instance;
private OkHttpClient client;
/**
* By using Singleton Pattern we can share cookie, client values.
**/
private NetworkManager() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
Context context = GlobalApplication.getGlobalApplicationContext();
ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));
builder.cookieJar(cookieJar);
builder.followRedirects(true);
builder.addInterceptor(new RedirectInterceptor());
File cacheDir = new File(context.getCacheDir(), "network");
if (!cacheDir.exists()) {
cacheDir.mkdir();
}
Cache cache = new Cache(cacheDir, 10 * 1024 * 1024);
builder.cache(cache);
builder.connectTimeout(30, TimeUnit.SECONDS);
builder.readTimeout(10, TimeUnit.SECONDS);
builder.writeTimeout(10, TimeUnit.SECONDS);
client = builder.build();
}
public static NetworkManager getInstance() {
if (instance == null) {
instance = new NetworkManager();
}
return instance;
}
public OkHttpClient getClient() {
return client;
}
}
In addition below is sample usage.
OkHttpClient client = NetworkManager.getInstance().getClient();
RequestBody formBody = new FormBody.Builder()
.add("userId", getTargetUserId())
.build();
Request request = new Request.Builder()
.url("www.test.com/insert.php")
.post(formBody)
.build();
client.newCall(request).enqueue(callback);
I need to get the XML file from the site. I'm learning to use Retrofit.
I need to make a request and attach my API key via the "X-AppId" header. It should look like this:
X-AppId: my key.
If I do this from the browser, I get the answer.
Through the retrofit I get the access
error 403 Forbidden code = 403, message = Forbidden, url = https: //
Tell me how it is implemented properly to receive an answer from the server code = 200
Here is my implementation:
public interface myAPIinterface {
#GET("/api/ru/index/route/?from=Minsk&to=Warsaw")
Call<Routes> getProducts();
}
This is the activity where I output to the log:
private void getProducts(){
final ProgressDialog loading = ProgressDialog.show(this,"Fetching Data","Please wait...",false,false);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
Log.d(TAG, "getProducts");
httpClient.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.addHeader("X-AppId:", "97377f7b702d7198e47a2bf12eec74")
.build();
return chain.proceed(request);
}
});
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://rasp.rw.by")
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
myAPIinterface api = retrofit.create(myAPIinterface.class);
Call<Routes> call = api.getProducts();
call.enqueue(new Callback<Routes>() {
#Override
public void onResponse(#NonNull Call<Routes> call, #NonNull Response<Routes> response) {
Log.d(TAG, "onResponse");
Log.d(TAG, String.valueOf(kk));
Log.d(TAG, String.valueOf(response));
loading.dismiss();}
#Override
public void onFailure(Call<Routes> call, Throwable throwable) {
loading.dismiss();
Log.d(TAG, "onFailure" + throwable);
}
});
this is a log:
Response{protocol=http/1.1, code=403, message=Forbidden,
url=https://rasp.rw.by/api/ru/index/route/?from=Minsk&to=Warsaw}
if I take third-party sites where there are no headers, I get a response of 200 without problems. What am I doing wrong in this case? Thank you.
Oh, man, what are you doing. You can use annotations like #Query, #Header, etc.
public interface myAPIinterface {
#GET("/api/ru/index/route")
Call<Routes> getProducts(#Header("X-AppId:") String YOUR_APP_ID,
#Query("from") String from,
#Query("to") String to)
}
Then you can create request like this:
Retrofit retrofit = new Retrofit.Builder().
.baseUrl("https://rasp.rw.by")
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
retrofit.create(myAPIinterface.class).getProducts(myId, "Minsk", "Warsaw").enqueue ...
How It can help? You forgot to add header at second retrofit and then you have 403 error. So, You must add annotations, and this will be the last mistake when you forgot to put value to header/query/etc.
I am building an Android APP where I use the Internet Game Database API through Mashape market place. I am using Retrofit for the get requests and getting data from the API requires an API key.
I got it to work but the API only return game ids and I want the game names and other information, but I am not sure how to add the fields. This is how Mashape query it:
HttpResponse<String> response = Unirest.get("https://igdbcom-internet-game-database-v1.p.mashape.com/games/?fields=name%2Crelease_dates")
.header("X-Mashape-Key", "API KEY HERE")
.header("Accept", "application/json")
.asString();
and this is my Retrofit Interface
public interface GamesAPIService {
#GET("/games/")
Call<List<GamesResponse>> gameList(#Query("mashape-key") String apikey);
}
I tried to use this
#GET("/games/?fields=name,release_dates")
But no luck, I also tried with #Field but didn't work either. Any ideas? Thanks.
Edit: Just to clarify when I add the "?fields=name,release_dates" I get 401 Unauthorized Error.
Firstly I think you need to add mashape key to all your request.
OkHttpClient httpClient = new OkHttpClient();
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("X-Mashape-Key", "API_KEY_HERE")
.addHeader("Accept", "application/json")
.build();
return chain.proceed(request);
}
});
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://igdbcom-internet-game-database-v1.p.mashape.com")
.client(httpClient)
.build();
And then this is information query.
public interface GamesAPIService {
#GET("/games")
Call<List<GamesResponse>> gameList(#Query("fields") String value);
}
And last thing for calling.
GamesAPIService gamesAPIService = retrofit.create(GamesAPIService.class);
Call<List<GamesResponse>> call = gamesAPIService.gameList("name,release_dates");
if (call!=null){
call.enqueue(new Callback<List<GamesResponse>>() {
#Override
public void onResponse(Call<List<GamesResponse>> call, Response<List<GamesResponse>> response) {
// handle success
}
#Override
public void onFailure(Throwable t) {
// handle failure
}
});
}
I am developing Android App interacting with Twitter using Fabric and Retrofit2 libraries. I want to display search timeline. My request URL is like this: https://api.twitter.com/1.1/friends/list.json?screen_name=xxx
The response body I got is null but I got the alert of bad authentication:215 and http error 400 in the debug mode.This is probably caused by invalid authentication of the request from my app.
The Twitter developer document said requests need to be authorized with OAuth and SSL certificated.
As for the OAuth issue, I wrote the request header based on the official document of twitter developer platform https://dev.twitter.com/oauth/overview/authorizing-requests
and create the header with okhttpclient and pass it to retrofit object.
The code for OAuth issue is like this.
public class TwitterClientApiClient extends TwitterApiClient {
private static final String TAG=TwitterClientApiClient.class.getSimpleName();
private static final MainApplication app=MainApplication.getInstance();
public static final String BASE_URL = "https://api.twitter.com/";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
final String authStr = app.authStr();
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("Accept", "application/json")
.header("Authorization", authStr)
.method(original.method(), original.body())
.build();
Headers okHeaders = request.headers();
Log.d(TAG,okHeaders.toString());
return chain.proceed(request);
}
});
OkHttpClient client = httpClient.build();
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
}
return retrofit;
}
public TwitterClientApiClient(TwitterSession session) {
super(session);
}
public FriendsService getFriendsService() {return getService(FriendsService.class);}
}
interface FriendsService {
#GET("/1.1/friends/list.json")
Call<FriendsResult> list(#Query("screen_name") String screen_name);
}
The following is the code making the request.
FriendsService apiService =
TwitterClientApiClient.getClient().create(FriendsService.class);
Call<FriendsResult> call = apiService.list(screenName);
Log.d(TAG, call.request().url().toString());
call.enqueue(new Callback<FriendsResult>() {
#Override
public void onResponse(Call<FriendsResult> call, Response<FriendsResult> response) {
//List<User> friends = response.body().getUsers();
Log.d(TAG,response.body().toString());
//Log.d(TAG, "Number of Friends: " + friends.size());
//String q = getQueryStr(friends);
//showSearchedTimeline(q);
}
#Override
public void onFailure(Call<FriendsResult>call, Throwable t) {
Log.e(TAG, t.toString());
}
});
However,according to https://oauth.net/core/1.0/#encoding_parameters
OAuth Authentication is done in three steps:
1.The Consumer obtains an unauthorized Request Token.
2.The User authorizes the Request Token.
3.The Consumer exchanges the Request Token for an Access Token.
My code which is based on references from the internet seems to do only Step 3 and thus the authentication is not complete. I wonder how to complete the whole authentication process of OAuth.
Also do I need to do sth in my code for SSL stuff?
Besides OAuth and SSL, any other security issue for request to twitter server I have overlooked?
Thanks in advance!
.header("Authorization", authStr)
Try with addHeader. You can activate the logs (useful to debug sometimes) using a logging interceptor. Ask the logger to show your headers, to see if that could be the problem. Available levels are here.
I tried to make oauth2 for android application. it has little bug.
My bug is It doesn't have header like Authorization when I redirect
MyCookieCode. It send Authorization when I was login. but It doesn't work when I redirect
public static Retrofit getLoginRetrofitOnAuthz() {
Retrofit.Builder builder = new Retrofit.Builder().baseUrl(ServerValue.AuthServerUrl).addConverterFactory(GsonConverterFactory.create());
if (LoginRetrofitAuthz == null) {
httpClientAuthz.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
String str = etUsername.getText().toString() + ":" + etPassword.getText().toString();
String Base64Str = "Basic " + Base64.encodeToString(str.getBytes(), Base64.NO_WRAP);
System.out.println(Base64Str);
Request request = chain.request().newBuilder().addHeader("Authorization", Base64Str).build();
return chain.proceed(request);
}
});
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
httpClientAuthz.cookieJar(new JavaNetCookieJar(cookieManager));
LoginRetrofitAuthz = builder.client(httpClientAuthz.build()).build();
}
return LoginRetrofitAuthz;
}
Server Result (Top-Login, Bottom, Redirect)
Do you know how to staying header on redirect ?
in fact the sinner is OkHttp, but not Retrofit.
OkHttp removes all authentication headers on purpose:
https://github.com/square/okhttp/blob/7cf6363662c7793c7694c8da0641be0508e04241/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!sameConnection(url)) {
requestBuilder.removeHeader("Authorization");
}
Here is the discussion of this issue: https://github.com/square/retrofit/issues/977
You could use the OkHttp authenticator. It will get called if there is a 401 error returned. So you could use it to re-authenticate the request.
httpClient.authenticator(new Authenticator() {
#Override
public Request authenticate(Route route, Response response) throws IOException {
return response.request().newBuilder()
.header("Authorization", "Token " + DataManager.getInstance().getPreferencesManager().getAuthToken())
.build();
}
});
However in my case server returned 403 Forbidden instead of 401. And I had to get
response.headers().get("Location");
in-place and create and fire another network request:
public Call<Response> getMoreBills(#Header("Authorization") String authorization, #Url String nextPage)