Retrofit unable to set new Token to Header of Requests - java

I have a retrofit Client that helps me set a header for any requests i make to my REST APIs. On user login i get the token from the server and set this token to the header of the requests. I save this token to SharedPreferences so that i can get it anytime i need to make requests to my REST APIs. The problem is that anytime i set a new token to my SharedPreferences file when a new user signs in, it still gets the old token instead of saving this new token to use for future requests.
This is my Retrofit Client below:
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String token) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okClient = new OkHttpClient();
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.create();
okClient.interceptors().add(chain -> chain.proceed(chain.request()));
okClient.interceptors().add(chain -> {
Request original = chain.request();
Request request = original.newBuilder()
.header(Config.X_AUTH_TOKEN, "Bearer" + " " + token)
.method(original.method(), original.body())
.build();
Log.d("Authorization", token);
return chain.proceed(request);
});
okClient.interceptors().add(logging);
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(Config.BASE_URL1)
.client(okClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
return retrofit;
}
}
this is my codes for setting and getting the token
public String getToken() {
return prefs.getString(AuthUser.USER_TOKEN, "");
}
public void setToken(String token) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(AuthUser.USER_TOKEN, token);
editor.apply();
}
this is where i call my set token method to save the new token to SharedPreference
authUser.setToken(token);

I completely don't see how this is surprising. Your RetrofitClient is a confusingly (and arguably badly written) singleton. Let's go through a typical situation where this will fail.
You launch your app with a previously saved token. At first everything works fine. At some point you call RetrofitClient.getClient(token) and all requests succeed. After some time the server invalidates the token. You probably get a 403 response from your server, lauch the login screen again and update your token in your SharedPreferences. Here is where your problems begin. Although you saved your new token correctly, your RetrofitClient will do what singletons do and continue to return the first instantiation of itself stored in the private static Retrofit retrofit filed.
A quick workaround would be to add an invalidate method to your RetrofitClient. Something like.
public static void invalidate() {
this.retrofit = null;
}
Call it when you get your 403 response, or when you logout.
PS: Please move the following line if (retrofit==null) { at the beginning of your getClient method. Creating a new okHttp client, for nothing, every time someone calls getClient is just wasteful.

You can write intercepter to execute each time before network request happen.Create new file as HeaderIntercepter
public class HeaderIntercepter implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String token = context.getSharedPreferences(FILENAME, MODE_PRIVATE).getString("TOKEN","");
Request tokenRequest = request.newBuilder()
.addHeader("Authorization", "Bearer " + token)
.addHeader("Content-Type", "application/json")
.build();
return chain
.proceed(tokenRequest);
}
}
Add intercepter to okHttpclient
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(new HeaderIntercepter());

Related

Is it possible to pass a string to a interface? Or at lest fetch from SharedPreferences inside a interface?

I'm trying to fetch a stored JWT from the SharedPreferences so I can send them as a Header in my requrest but I'm not able to get that data inside the API interface. Is this possible?
Thanks
SOLVED:
For anyone looking for this: you can pass a Header as a param, ex.:
#FormUrlEncoded
#POST("users/getUser")
Call<String> getUser(
#Header("Token") String token,
#Field("user") String user
);
also you can use an integrated Interceptor to adding this token on ALL requests like this:
public class AuthInterceptor
implements Interceptor {
#Override
public Response intercept(Chain chain)
throws IOException {
Request request = chain.request();
request = request.newBuilder()
.addHeader("Token", new MySharedPref().getToken())
.build();
return chain.proceed(request);
}
}
and after that add an instance of it on your OkHttpClient :
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addNetworkInterceptor(new AuthInterceptor());
Retrofit retrofit = new Retrofit.Builder()
...
.client(httpClient.build())
.build();

Basic authorization in retrofit

Interface:
#GET("burrowedbooks/")
Call<JsonArray> getCategoryList(#Header("Authorization") String token);
Usage:
private LibraryAPi service;
Retrofit retrofit = new Retrofit.Builder()
//.client(client)
.baseUrl(String.valueOf(R.string.base_url))
.addConverterFactory(GsonConverterFactory.create())
.build();
service = retrofit.create(LibraryAPi.class);
// Extract token from Shared Preferences.
SharedPreferences prefs = getActivity().getSharedPreferences(getString(R.string.login_data), MODE_PRIVATE);
String token = "Bearer "+prefs.getString("token","");
Call<JsonArray> categoryListResponseCall = service.getCategoryList(token);
categoryListResponseCall.enqueue(new Callback<JsonArray>() {
#Override
public void onResponse(Call<JsonArray> call, Response<JsonArray> response) {
int statusCode = response.code();
Toast.makeText(getContext(), ""+statusCode, Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<JsonArray> call, Throwable t) {
}
});
I'm trying to send authentication token stored in shared preferences. The code above is not working. It returns 403 forbidden status code. What is the correct way to send authentication header?
You are wrong at .baseUrl(String.valueOf(R.string.base_url))
You should get string from resource using .baseUrl(getActivity().getString(R.string.base_url))
But your code will not send data to the server and onFailure would be called.
If you get the string properly and still are getting 403, you may want to verify your back end implementation using postman.
Also you can create a custom interceptor to add your header automatically on new requests.
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("Authorization", token)
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
}
OkHttpClient client = httpClient.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
Also, check if token is received good from SharedPreferences. Looks odd how you read it.

Dagger + Retrofit. Adding auth headers at runtime

I'm wondering if there is a way for Dagger to know that it should recreate an object when new data is available.
The instance I am speaking of is with the request headers I have for retrofit. At some point (when the user logs in) I get a token that I need to add to the headers of retrofit to make authenticated requests. The issue is, I'm left with the same unauthenticated version of retrofit. Here's my injection code:
#Provides
#Singleton
OkHttpClient provideOkHttpClient(Cache cache) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.cache(cache).build();
client
.newBuilder()
.addInterceptor(
chain -> {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.addHeader("Accept", "Application/JSON");
Request request = requestBuilder.build();
return chain.proceed(request);
}).build();
return client;
}
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create())
.baseUrl(mBaseUrl)
.client(okHttpClient)
.build();
return retrofit;
}
#Provides
#Singleton
public NetworkService providesNetworkService(Retrofit retrofit) {
return retrofit.create(NetworkService.class);
}
Any ideas on how to make this work?
I personally created an okhttp3.Interceptor that does that for me, which I update once I have the required token. It looks something like:
#Singleton
public class MyServiceInterceptor implements Interceptor {
private String sessionToken;
#Inject public MyServiceInterceptor() {
}
public void setSessionToken(String sessionToken) {
this.sessionToken = sessionToken;
}
#Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder requestBuilder = request.newBuilder();
if (request.header(NO_AUTH_HEADER_KEY) == null) {
// needs credentials
if (sessionToken == null) {
throw new RuntimeException("Session token should be defined for auth apis");
} else {
requestBuilder.addHeader("Cookie", sessionToken);
}
}
return chain.proceed(requestBuilder.build());
}
}
In the corresponding dagger component, I expose this interceptor so I can set the sessionToken when I need to.
That is some stuff that Jake talked about it his talk Making Retrofit Work For You.
Please consider using the approach mentioned by #oldergod as it is the "official" and much better way, whereas the approaches mentioned below are not advised, they may be considered as workarounds.
You have a couple of options.
As soon as you get the token, you have to null out the component that provided you the Retrofit instance, create a new component and ask for a new Retrofit instance, which will be instantiated with necessary okhttp instance.
A fast and bad one - Save the token in SharedPreferences, create okHttp header, which will apply token reading from SharedPreferences. If there is none - send no token header.
Even uglier solution - declare a static volatile String field, and do the same thing like in step 2.
Why the second option is bad? Because on each request you would be polling disk and fetch data from there.
Created custom RequestInterceptor with #Inject constructor
RequestInterceptor
#Singleton
class
RequestInterceptor #Inject constructor(
private val preferencesHelper: PreferencesHelper,
) : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var newRequest: Request = chain.request()
newRequest = newRequest.newBuilder()
.addHeader(
"AccessToken",
preferencesHelper.getAccessTokenFromPreference()
)
.build()
Log.d(
"OkHttp", String.format(
"--> Sending request %s on %s%n%s",
newRequest.url(),
chain.connection(),
newRequest.headers()
)
);
return chain.proceed(newRequest)
}
ApplicationModule
#Module(includes = [AppUtilityModule::class])
class ApplicationModule(private val application: AppController) {
#Provides
#Singleton
fun provideApplicationContext(): Context = application
#Singleton
#Provides
fun provideSharedPreferences(): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(application.applicationContext)
}
PreferencesHelper
#Singleton
class PreferencesHelper
#Inject constructor(
private val context: Context,
private val sharedPreferences: SharedPreferences
) {
private val PREF_KEY_ACCESS_TOKEN = "PREF_KEY_ACCESS_TOKEN"
fun getAccessTokenFromPreference(): String? {
return sharedPreferences.getString(PREF_KEY_ACCESS_TOKEN, null)
}
}
Well tested and working
public OkHttpClient getHttpClient(Context context) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.callTimeout(60,TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.addInterceptor(logging)
.addInterceptor(chain -> {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + Utility.getSharedPreferencesString(context, API.AUTHORIZATION))
.build();
return chain.proceed(newRequest);
})
.build();
}
Earlier I was wondering, if session expires and user login again, will this interceptor replace the existing auth, but fortunately it is working fine.

Retrofit add header with token and id

I have a problem with getting authenticated user. Before it I got token and user id. Now i need to get user from server using access token and id.
I have header format
Now I'am trying to add header with user token and id using interceptor.
My code:
Interceptor interceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader("Accept", "application/json")
.addHeader("authorization", token) <-??
.addHeader("driver_id", id) <-??
.build();
return chain.proceed(newRequest);
}
};
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
okHttpBuilder.addInterceptor(interceptor);
OkHttpClient okHttpClient = okHttpBuilder.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
Interface:
#GET("driver/v1/driver")
Call<Driver> getAuthorizedDriver();
Different variants throws 401 error, don't know what to do
Log:
I/Response code: 401
I/Response message: Unauthorized`
I got it.
It's must look like:
#GET("driver/v1/driver")
Call<Driver> getAuthorizedDriver(#Header("authorization") String auth);
And auth:
Call<Driver> call = apiInterface.getAuthorizedDriver("Token token=" + token + ", driver_id=" + id);
Try to pass the header values via the method call:
#GET("driver/v1/driver")
Call<Driver> getAuthorizedDriver(#Header("authorization") String token,
#Header("driver_id") Integer id);
You also wouldn't have to deal with this huge chunk of Interceptor code

Retrofit get a parameter from a redirect URL

I am using Retrofit.
I have an endpoint that redirects to another endpoint. The latter (the endpoint that I end up at) has a parameter in its URL that I need. What is the best way to get the value of this parameter?
I cannot even figure out how to get the URL that I am redirected to, using Retrofit.
OkHttp's Response will give you the wire-level request (https://square.github.io/okhttp/3.x/okhttp/okhttp3/Response.html#request--). This will be the Request that initiated the Response from the redirect. The Request will give you its HttpUrl, and HttpUrl can give you its parameters' keys and values, paths, etc.
With Retrofit 2, simply use retrofit2.Response.raw() to get the okhttp3.Response and follow the above.
I am using retrofit. And I can get the redirect url following this way :
private boolean handleRedirectUrl(RetrofitError cause) {
if (cause != null && cause.getResponse() != null) {
List<Header> headers = cause.getResponse().getHeaders();
for (Header header : headers) {
//KEY_HEADER_REDIRECT_LOCATION = "Location"
if (KEY_HEADER_REDIRECT_LOCATION.equals(header.getName())) {
String redirectUrl = header.getValue();
return true;
}
}
}
return false;
}
Hope it could help someone.
Solution for this would be to use an interceptor e.g.
private Interceptor interceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Response response = chain.proceed(chain.request());
locationHistory.add(response.header("Location"));
return response;
}
};
Add the interceptor to your HttpClient and add that to Retrofit(using 2.0 for this example)
public void request(String url) {
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.followRedirects(true);
client.addNetworkInterceptor(interceptor);
OkHttpClient httpClient = client.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient)
.build();
}
Now you have full access the the entire redirect history.

Categories