We are using Retrofit in our Android app, to communicate with an OAuth2 secured server. Everything works great, we use the RequestInterceptor to include the access token with each call.
However there will be times, when the access token will expire, and the token needs to be refreshed. When the token expires, the next call will return with an Unauthorized HTTP code, so that's easy to monitor.
We could modify each Retrofit call the following way:
In the failure callback, check for the error code, if it equals Unauthorized, refresh the OAuth token, then repeat the Retrofit call.
However, for this, all calls should be modified, which is not an easily maintainable, and good solution.
Is there a way to do this without modifying all Retrofit calls?
Please do not use Interceptors to deal with authentication.
Currently, the best approach to handle authentication is to use the new Authenticator API, designed specifically for this purpose.
OkHttp will automatically ask the Authenticator for credentials when a response is 401 Not Authorised retrying last failed request with them.
public class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
Attach an Authenticator to an OkHttpClient the same way you do with Interceptors
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);
Use this client when creating your Retrofit RestAdapter
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(ENDPOINT)
.setClient(new OkClient(okHttpClient))
.build();
return restAdapter.create(API.class);
If you are using Retrofit >= 1.9.0 then you could make use of OkHttp's new Interceptor, which was introduced in OkHttp 2.2.0. You would want to use an Application Interceptor, which permits you to retry and make multiple calls.
Your Interceptor could look something like this pseudocode:
public class CustomInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
if (response shows expired token) {
// close previous response
response.close()
// get a new token (I use a synchronous Retrofit call)
// create a new request and modify it accordingly using the new token
Request newRequest = request.newBuilder()...build();
// retry the request
return chain.proceed(newRequest);
}
// otherwise just pass the original response on
return response;
}
}
After you define your Interceptor, create an OkHttpClient and add the interceptor as an Application Interceptor.
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new CustomInterceptor());
And finally, use this OkHttpClient when creating your RestAdapter.
RestService restService = new RestAdapter().Builder
...
.setClient(new OkClient(okHttpClient))
.create(RestService.class);
Warning: As Jesse Wilson (from Square) mentions here, this is a dangerous amount of power.
With that being said, I definitely think this is the best way to handle something like this now. If you have any questions please don't hesitate to ask in a comment.
TokenAuthenticator depends an a service class. The service class depends on an OkHttpClient instance. To create an OkHttpClient I need the TokenAuthenticator. How can I break this cycle? Two different OkHttpClients? They are going to have different connection pools..
If you have, say, a Retrofit TokenService that you need inside your Authenticator but you would only like to set up one OkHttpClient you can use a TokenServiceHolder as a dependency for TokenAuthenticator. You would have to maintain a reference to it at the application (singleton) level. This is easy if you are using Dagger 2, otherwise just create class field inside your Application.
In TokenAuthenticator.java
public class TokenAuthenticator implements Authenticator {
private final TokenServiceHolder tokenServiceHolder;
public TokenAuthenticator(TokenServiceHolder tokenServiceHolder) {
this.tokenServiceHolder = tokenServiceHolder;
}
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
//is there a TokenService?
TokenService service = tokenServiceHolder.get();
if (service == null) {
//there is no way to answer the challenge
//so return null according to Retrofit's convention
return null;
}
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken().execute();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
// Null indicates no attempt to authenticate.
return null;
}
In TokenServiceHolder.java:
public class TokenServiceHolder {
TokenService tokenService = null;
#Nullable
public TokenService get() {
return tokenService;
}
public void set(TokenService tokenService) {
this.tokenService = tokenService;
}
}
Client setup:
//obtain instance of TokenServiceHolder from application or singleton-scoped component, then
TokenAuthenticator authenticator = new TokenAuthenticator(tokenServiceHolder);
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(tokenAuthenticator);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(okHttpClient)
.build();
TokenService tokenService = retrofit.create(TokenService.class);
tokenServiceHolder.set(tokenService);
If you are using Dagger 2 or a similar dependency injection framework there are some examples in the answers to this question
Using TokenAuthenticator like #theblang answer is a correct way for handle refresh_token.
Here is my implement (I have using Kotlin, Dagger, RX but you may use this idea for implement to your case)
TokenAuthenticator
class TokenAuthenticator #Inject constructor(private val noneAuthAPI: PotoNoneAuthApi, private val accessTokenWrapper: AccessTokenWrapper) : Authenticator {
override fun authenticate(route: Route, response: Response): Request? {
val newAccessToken = noneAuthAPI.refreshToken(accessTokenWrapper.getAccessToken()!!.refreshToken).blockingGet()
accessTokenWrapper.saveAccessToken(newAccessToken) // save new access_token for next called
return response.request().newBuilder()
.header("Authorization", newAccessToken.token) // just only need to override "Authorization" header, don't need to override all header since this new request is create base on old request
.build()
}
}
For prevent dependency cycle like #Brais Gabin comment, I create 2 interface like
interface PotoNoneAuthApi { // NONE authentication API
#POST("/login")
fun login(#Body request: LoginRequest): Single<AccessToken>
#POST("refresh_token")
#FormUrlEncoded
fun refreshToken(#Field("refresh_token") refreshToken: String): Single<AccessToken>
}
and
interface PotoAuthApi { // Authentication API
#GET("api/images")
fun getImage(): Single<GetImageResponse>
}
AccessTokenWrapper class
class AccessTokenWrapper constructor(private val sharedPrefApi: SharedPrefApi) {
private var accessToken: AccessToken? = null
// get accessToken from cache or from SharePreference
fun getAccessToken(): AccessToken? {
if (accessToken == null) {
accessToken = sharedPrefApi.getObject(SharedPrefApi.ACCESS_TOKEN, AccessToken::class.java)
}
return accessToken
}
// save accessToken to SharePreference
fun saveAccessToken(accessToken: AccessToken) {
this.accessToken = accessToken
sharedPrefApi.putObject(SharedPrefApi.ACCESS_TOKEN, accessToken)
}
}
AccessToken class
data class AccessToken(
#Expose
var token: String,
#Expose
var refreshToken: String)
My Interceptor
class AuthInterceptor #Inject constructor(private val accessTokenWrapper: AccessTokenWrapper): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val authorisedRequestBuilder = originalRequest.newBuilder()
.addHeader("Authorization", accessTokenWrapper.getAccessToken()!!.token)
.header("Accept", "application/json")
return chain.proceed(authorisedRequestBuilder.build())
}
}
Finally, add Interceptor and Authenticator to your OKHttpClient when create service PotoAuthApi
Demo
https://github.com/PhanVanLinh/AndroidMVPKotlin
Note
Authenticator flow
Example API getImage() return 401 error code
authenticate method inside TokenAuthenticator will fired
Synchronize noneAuthAPI.refreshToken(...) called
After noneAuthAPI.refreshToken(...) response -> new token will add to header
getImage() will AUTO called with new header (HttpLogging WILL NOT log this call) (intercept inside AuthInterceptor WILL NOT CALLED)
If getImage() still failed with error 401, authenticate method inside TokenAuthenticator will fired AGAIN and AGAIN then it will throw error about call method many time(java.net.ProtocolException: Too many follow-up requests). You can prevent it by count response. Example, if you return null in authenticate after 3 times retry, getImage() will finish and return response 401
If getImage() response success => we will result the result normally (like you call getImage() with no error)
Hope it help
As Brais Gabin said in the comment I had the problem that TokenAuthenticator depends on a service class. The service class depends on an OkHttpClient instance and to create an OkHttpClient I need the TokenAuthenticator.
So how I broke this cycle?
I created a new okHttpClient object, a new Retrofit object and with that object I did the call to get the new token using the refreshToken ( check getUpdatedToken() function)
class TokenAuthenticator : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return runBlocking {
// 1. Refresh your access_token using a synchronous api request
val response = getUpdatedToken(refreshToken)
//2. In my case here I store the new token and refreshToken into SharedPreferences
response.request.newBuilder()
.header("Authorization", "Bearer ${tokenResponse.data?.accessToken}")
.build()
// 3. If there's any kind of error I return null
}
}
private suspend fun getUpdatedToken( refreshToken: String): TokenResponse {
val okHttpClient = OkHttpClient().newBuilder()
.addInterceptor(errorResponseInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
val service = retrofit.create(RefreshTokenApi::class.java)
return service.refreshToken(refreshToken)
}
}
RefreshTokenApi
interface RefreshTokenApi {
#FormUrlEncoded
#POST("refreshToken")
suspend fun refreshToken(
#Field("refresh_token") refreshToeken: String
): TokenResponse
}
In this project I'm using Koin and I configured this way:
object RetrofigConfig {
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
fun provideOkHttpClient(
tokenAuthenticator: TokenAuthenticator
): OkHttpClient {
return OkHttpClient().newBuilder()
.authenticator(tokenAuthenticator)
.build()
}
fun provideServiceApi(retrofit: Retrofit): ServiceApi {
return retrofit.create(ServiceApi::class.java)
}
}
The important line there is OkHttpClient().newBuilder().authenticator(tokenAuthenticator)
Because this is the first time I'm implementing this I don't know if this is the best way but is the way it is working in my project.
I know this an old thread, but just in case someone stumbled in it.
TokenAuthenticator depends an a service class. The service class depends on an OkHttpClient instance. To create an OkHttpClient I need the TokenAuthenticator. How can I break this cycle? Two different OkHttpClients? They are going to have different connection pools..
I was facing the same problem, but I wanted to create only one OkHttpClient becuase I don't think that I need another one for just the TokenAuthenticator itself, I was using Dagger2, so I ended up providing the service class as Lazy injected in the TokenAuthenticator, you can read more about Lazy injection in dagger 2 here, but it's like basically saying to Dagger to NOT go and create the service needed by the TokenAuthenticator right away.
You can refer to this SO thread for sample code: How to resolve a circular dependency while still using Dagger2?
Using one Interceptor (inject the token) and one Authenticator (refresh operations) do the job but:
I had a double call problem too: the first call always returned a 401:
the token wasn't inject at the first call (interceptor) and the authenticator was called: two requests were made.
The fix was just to reaffect the request to the build in the Interceptor:
BEFORE:
private Interceptor getInterceptor() {
return (chain) -> {
Request request = chain.request();
//...
request.newBuilder()
.header(AUTHORIZATION, token))
.build();
return chain.proceed(request);
};
}
AFTER:
private Interceptor getInterceptor() {
return (chain) -> {
Request request = chain.request();
//...
request = request.newBuilder()
.header(AUTHORIZATION, token))
.build();
return chain.proceed(request);
};
}
IN ONE BLOCK:
private Interceptor getInterceptor() {
return (chain) -> {
Request request = chain.request().newBuilder()
.header(AUTHORIZATION, token))
.build();
return chain.proceed(request);
};
}
Hope it helps.
Edit: I didn't find a way to avoid the first call to always returning 401 using only the authenticator and no interceptor
You can try creating a base class for all your loaders in which you would be able to catch a particular exception and then act as you need.
Make all your different loaders extend from the base class in order to spread the behaviour.
After Long research, I customized Apache client to handle Refreshing AccessToken For Retrofit In which you send access token as parameter.
Initiate your Adapter with cookie Persistent Client
restAdapter = new RestAdapter.Builder()
.setEndpoint(SERVER_END_POINT)
.setClient(new CookiePersistingClient())
.setLogLevel(RestAdapter.LogLevel.FULL).build();
Cookie Persistent client which maintains cookies for all requests and checks with each request response, if it is unauthorized access ERROR_CODE = 401, refresh access token and recall the request, else just processes request.
private static class CookiePersistingClient extends ApacheClient {
private static final int HTTPS_PORT = 443;
private static final int SOCKET_TIMEOUT = 300000;
private static final int CONNECTION_TIMEOUT = 300000;
public CookiePersistingClient() {
super(createDefaultClient());
}
private static HttpClient createDefaultClient() {
// Registering https clients.
SSLSocketFactory sf = null;
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore
.getDefaultType());
trustStore.load(null, null);
sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params,
CONNECTION_TIMEOUT);
HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("https", sf, HTTPS_PORT));
// More customization (https / timeouts etc) can go here...
ClientConnectionManager cm = new ThreadSafeClientConnManager(
params, registry);
DefaultHttpClient client = new DefaultHttpClient(cm, params);
// Set the default cookie store
client.setCookieStore(COOKIE_STORE);
return client;
}
#Override
protected HttpResponse execute(final HttpClient client,
final HttpUriRequest request) throws IOException {
// Set the http context's cookie storage
BasicHttpContext mHttpContext = new BasicHttpContext();
mHttpContext.setAttribute(ClientContext.COOKIE_STORE, COOKIE_STORE);
return client.execute(request, mHttpContext);
}
#Override
public Response execute(final Request request) throws IOException {
Response response = super.execute(request);
if (response.getStatus() == 401) {
// Retrofit Callback to handle AccessToken
Callback<AccessTockenResponse> accessTokenCallback = new Callback<AccessTockenResponse>() {
#SuppressWarnings("deprecation")
#Override
public void success(
AccessTockenResponse loginEntityResponse,
Response response) {
try {
String accessToken = loginEntityResponse
.getAccessToken();
TypedOutput body = request.getBody();
ByteArrayOutputStream byte1 = new ByteArrayOutputStream();
body.writeTo(byte1);
String s = byte1.toString();
FormUrlEncodedTypedOutput output = new FormUrlEncodedTypedOutput();
String[] pairs = s.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
if (URLDecoder.decode(pair.substring(0, idx))
.equals("access_token")) {
output.addField("access_token",
accessToken);
} else {
output.addField(URLDecoder.decode(
pair.substring(0, idx), "UTF-8"),
URLDecoder.decode(
pair.substring(idx + 1),
"UTF-8"));
}
}
execute(new Request(request.getMethod(),
request.getUrl(), request.getHeaders(),
output));
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void failure(RetrofitError error) {
// Handle Error while refreshing access_token
}
};
// Call Your retrofit method to refresh ACCESS_TOKEN
refreshAccessToken(GRANT_REFRESH,CLIENT_ID, CLIENT_SECRET_KEY,accessToken, accessTokenCallback);
}
return response;
}
}
here is my code working for me. may be helpful for some one
class AuthenticationInterceptorRefreshToken #Inject
constructor( var hIltModules: HIltModules,) : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val response = chain.proceed(originalRequest)
if (response.code == 401) {
synchronized(this) {
val originalRequest = chain.request()
val authenticationRequest = originalRequest.newBuilder()
.addHeader("refreshtoken", " $refreshToken")
.build()
val initialResponse = chain.proceed(authenticationRequest)
when (initialResponse.code) {
401 -> {
val responseNewTokenLoginModel = runBlocking {
hIltModules.provideAPIService().refreshToken()
}
when (responseNewTokenLoginModel.statusCode) {
200 -> {
refreshToken = responseNewTokenLoginModel.refreshToken
access_token = responseNewTokenLoginModel.accessToken
val newAuthenticationRequest = originalRequest.newBuilder()
.header("refreshtoken",
" $refreshToken")
.build()
return chain.proceed(newAuthenticationRequest)
}
else -> {
return null!!
}
}
}
else -> return initialResponse
}
}
}; return response
}
To anyone who wanted to solve concurrent/parallel calls when refreshing token. Here's a workaround
class TokenAuthenticator: Authenticator {
override fun authenticate(route: Route?, response: Response?): Request? {
response?.let {
if (response.code() == 401) {
while (true) {
if (!isRefreshing) {
val requestToken = response.request().header(AuthorisationInterceptor.AUTHORISATION)
val currentToken = OkHttpUtil.headerBuilder(UserService.instance.token)
currentToken?.let {
if (requestToken != currentToken) {
return generateRequest(response, currentToken)
}
}
val token = refreshToken()
token?.let {
return generateRequest(response, token)
}
}
}
}
}
return null
}
private fun generateRequest(response: Response, token: String): Request? {
return response.request().newBuilder()
.header(AuthorisationInterceptor.USER_AGENT, OkHttpUtil.UA)
.header(AuthorisationInterceptor.AUTHORISATION, token)
.build()
}
private fun refreshToken(): String? {
synchronized(TokenAuthenticator::class.java) {
UserService.instance.token?.let {
isRefreshing = true
val call = ApiHelper.refreshToken()
val token = call.execute().body()
UserService.instance.setToken(token, false)
isRefreshing = false
return OkHttpUtil.headerBuilder(token)
}
}
return null
}
companion object {
var isRefreshing = false
}
}
Related
I am building an android app and i am using Retrofit to retrieve data from API. In this app i have to make 3 calls. The first one is working fine. The code for the first one is below. I have one class
public class APIClient {
private static Retrofit retrofit = null;
static Retrofit getClient(){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
retrofit = new Retrofit.Builder()
.baseUrl("https://api_app.com")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit;
}
}
Also i have this interface
#Headers({
"AppId: 3a97b932a9d449c981b595",
"Content-Type: application/json",
"appVersion: 5.10.0",
"apiVersion: 3.0.0"
})
#POST("/users/login")
Call<MainUserLogin> logInUser(#Body LoginBody loginBody);
The code of the Actvity is this
call.enqueue(object : Callback<MainUserLogin> {
override fun onResponse(call: Call<MainUserLogin>, response: Response<MainUserLogin>) {
if (response.code().toString().equals("200")){
val resource = response.body()
bearerToken = resource.session.bearerToken
if (bearerToken.isNotEmpty() && bearerToken.isNotBlank()){
val sharedPreferences = getSharedPreferences("Settings", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putString("bearerToken", bearerToken)
editor.commit()
BearerToken.bearerToken = bearerToken
val i = Intent(this#LoginActivity, UserAccountsActivity::class.java)
i.putExtra("bearerToken", bearerToken)
startActivity(i)
}else{
Toast.makeText(applicationContext, "Please try again.", Toast.LENGTH_LONG).show()
}
}else{
println("edwedw "+response.errorBody().string())
Toast.makeText(applicationContext, "Incorrect email address or password. Please check and try again.", Toast.LENGTH_LONG).show()
}
}
override fun onFailure(call: Call<MainUserLogin>, t: Throwable) {
call.cancel()
}
})
This call is working fine.
With this call i am getting one token. The problem is that i have to pass this token as header to make the second call. So, the second call will be like this.
#Headers({
"AppId: 3a97b932a9d449c981b595",
"Content-Type: application/json",
"appVersion: 5.10.0",
"apiVersion: 3.0.0",
"Authorization: "+***Token***
})
#GET("/products")
Call<MainUserLogin> getUseraccounts ();
Is there any way to pass the variable from the Activity to the interface to make the Api request?
Thank you very much.
Using Retrofit you can call API's with multiple headers as follows
#GET("/products")
Call<MainUserLogin> getUseraccounts(#Header("AppId") String appId, #Header("Content-Type") String contentType, #Header("appVersion") String appVersion, #Header("apiVersion") String apiVersion, #Header("Authorization") String token);
Instead of
#Headers({
"AppId: 3a97b932a9d449c981b595",
"Content-Type: application/json",
"appVersion: 5.10.0",
"apiVersion: 3.0.0",
"Authorization: "+***Token***
})
#GET("/products")
Call<MainUserLogin> getUseraccounts ();
this. When you call getUseraccounts method you can parse the token that you created from the previous endpoint.
Try this and let me know your feedback. Thanks!
Once you receive the token, you should save this token in a global repository since the auth token is something that your app will need in order to make further authenticated api calls.
After that, define a AuthorizationHeaderInterceptor which will extend okhttp3.Interceptor. Override the intercept method of this interceptor to add auth token to your request.
#Override
public Response intercept(#NonNull Chain chain) {
return completeRequest(chain);
}
private Response completeRequest(#NonNull Interceptor.Chain chain) {
AuthToken authToken = authTokenRepository.get();
Request.Builder requestBuilder = chain.request().newBuilder();
if (authToken != null && chain.request().header(Authorization.NAME) == null) {
requestBuilder.addHeader(Authorization.NAME, Authorization.getValue(authToken.getIdToken()));
}
Request request = requestBuilder.build();
try {
return chain.proceed(request);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
The interceptor can be added when you build your okhttpClient.
okHttpClientBuilder.addInterceptor(new AuthorizationHeaderInterceptor(authTokenRepository))
Note that the Authorization class is simple convenience class which encapsulates the authorization header name and value format.
public class Authorization {
public static final String NAME = "Authorization";
#NonNull
public static String getValue(#NonNull String accessToken) {
return String.format("Bearer %s", accessToken);
}
}
It seems general consensus recommends storing authentication token in SharedPreferences, as this post suggests. However, I have a user object with several attributes, including an id, email, name, authentication token, and possibly more attributes. Should I store the authentication token ALONE in SharedPreferences and then for each activity, find the user by the authentication token:
String authenticationToken = User.findByAuthenticationToken(sharedPreferences.getString("authenticationToken"));
or should I convert the object to JSON and then store the entire user object in SharedPreferences? And then for each activity, deserialize it. This seems less efficient.
You can store token in SharedPrefs, or use DI, as #Francesc said.
If you are using retrofit, I advice you to create singleton (or module) with retrofit instance, and add request interceptor to OkHttpClient.
private OkHttpClient buildClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//do any another stuff
builder.addInterceptor(new RequestAuthInterceptor());
return builder.build();
}
public static class RequestAuthInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
String jwtToken = SharedPrefs.getKeyJwt();
if (jwtToken != null) {
Request.Builder builder = chain.request().newBuilder();
builder.addHeader("Authorization", jwtToken);
return chain.proceed(builder.build());
} else {
return chain.proceed(chain.request());
}
}
}
Storing that data in preferences simply to pass it from one activity to another is inefficient. If you are using Dagger2 or any other dependency injection framework, you could consider having a User module that is created when the user logs in and holds the relevant user information. You can then access this module from your activities and read the user info.
If you're not using dependency injection you can do something similar with a Singleton class that you would populate when logging in and clear when logging out.
Edit: here are some extracts from one of my apps (this is in Kotlin)
Retrieving the token for API requests:
val httpClientBuilder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
httpClientBuilder.addInterceptor(logging)
}
httpClientBuilder.addInterceptor { chain ->
var request = chain.request()
val token = ObjectGraphController.userComponent?.token
token?.apply {
request = request.newBuilder().addHeader("Session-Id", this).build()
}
chain.proceed(request)
}
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(<YOUR ENDPOINT>)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.client(httpClientBuilder.build())
.build()
User module:
#Module
class UserModule(val token: String) {
#Provides
#UserScope
fun providesToken() = token
}
Set/clear component when logging in and out:
object ObjectGraphController {
lateinit var objectGraph: ObjectGraph
var userComponent: UserComponent? = null
private set
fun setUserComponent(token: String?) {
if (token != null) {
userComponent = objectGraph.userComponent(UserModule(token))
} else {
userComponent = null
}
}
}
I want to update access token in network request.But there is some difficulty using Dagger and Retrofit.
๐ขSorry,my English is not good , so give you an example may be much clear.Starting from scratch, my idea is like this:
provide an access token saved in shared preference
#Provides
#ForOauth
Preference<String> provideAccessToken(RxSharedPreferences prefs) {
return prefs.getString(PrefsUtils.KEY_ACCESS_TOKEN);
}
use access token to create an interceptor and added into okhttp client
#Provides
#Singleton
#Named("Cached")
public OkHttpClient provideOkHttpClientWithCache(Application application, #ForOauth OauthInterceptor oauthInterceptor) {
...
builder.addInterceptor(oauthInterceptor);
...
}
and I provide the OauthInterceptor instance by its constructor
#Inject
public OauthInterceptor(#ForOauth Preference<String> accessToken) {
this.accessToken = accessToken;
Timber.tag("OauthInterceptor");
}
But cause the okhttp client is a singleton,it won't change when the access token in prefs updates.An alternative way I thought that may work is to use a custom scope like #ForOauth or something, but it's just a rough sketch...
By the way, I have another idea like this:
get the access token from prefs in the intercept() method , so every time I can have a request header which contains the latest access token.
#Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
if (accessToken.isSet()) {
// Preference<String> accessToken
builder.header("Authorization", ACCESS_TYPE + accessToken.get());
} else {
builder.header("Authorization", "Bearer xxxxxx");
}
return chain.proceed(builder.build());
}
But I haven't really experimented with this idea,and I think it's not right ๐
I wonder whether I have to create a new okhttp client instance every time or I can just update the access token then the okhttp client singleton can refresh its interceptor...
So could you please give me some advice , or a simple working example.
Thanks in advance ๐
Hmmmm, I've done this many times and never noticed any issues with the access token refresh not making its way down the chain to OkHttp. Here's a typical setup I use in apps:
#Provides #Singleton
SharedPreferences providePreferences(Context ctx) {
return new SharedPreferences(ctx);
}
#Provides #Singleton
HttpLoggingInterceptor provideLoggingInterceptor(){
return new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
}
#Provides #Singleton
OkHttpClient provideClient(HttpLoggingInterceptor interceptor, SharedPreferences prefs){
return new OkHttpClient.Builder()
.addNetworkInterceptor(chain -> {
// Add Auth Header
String token = prefs.accessToken().get();
if(token == null) token = "";
Request request = chain.request().newBuilder().addHeader("Authorization", token).build();
return chain.proceed(request);
})
.addInterceptor(interceptor)
.build();
}
#Provides #Singleton
Retrofit provideRetrofit(#ApiUrl String url, OkHttpClient client){
return new Retrofit.Builder()
.baseUrl(url)
.client(client)
.addConverterFactory(LoganSquareConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
SharedPreferences is just a class I've abstracted some of the RxSharedPreferences logic into. Can also just #Inject it wherever you need it in the app that way too, which is nice. Here's a simple version of that class just for fun:
public class SharedPreferences {
// Constants and variables
private static final String PREFERENCE_FILENAME = BuildConfig.APPLICATION_ID + ".prefs";
private static final String PREF_ACCESS_TOKEN= "pref_access_token";
private RxSharedPreferences mRxSharedPrefs;
// Constructor
public SharedPreferences(Context context) {
mRxSharedPrefs = RxSharedPreferences.create(context.getSharedPreferences(PREFERENCE_FILENAME, Context.MODE_PRIVATE));
}
// Helper methods
public Preference<String> accessToken() { return mRxSharedPrefs.getString(PREF_ACCESS_TOKEN, ""); }
public void logout() { accessToken().delete(); }
}
I am new to Retrofit Library. I am working on an app in which I've to make multiple API calls, but this problem sticks me when I tried to make my first API Call...
I am facing the issue that whenever I used to call retrofit's Asynchronous call method then the functionality inside onResponse method is running 2 times...
This is my code when I am calling the API call asynchronously...
final ApiModule apiService = ApiServiceGenerator.createService(ApiModule.class);
Call <ConfigResponse> call = apiService.getConfig();
call.enqueue(new Callback<ConfigResponse>() {
#Override
public void onResponse(Call<ConfigResponse> call, Response<ConfigResponse> response) {
try {
if (response.isSuccessful()) {
Log.e("MyTag", "This is running");
}
} catch(Exception e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ConfigResponse> call, Throwable t) {
e.printStackTrace();
}
});
As soon as I run the App on the device and when I see my android studio's logger, its is showing me the log message as -
E/MyTag: This is running
E/MyTag: This is running
It seems here that its running for 2 times..!!
I cannot understand that why is it running 2 times.
Please help me out with this...
Just for more help...
I've implemented my code like this.
ApiModule Interface (where I defined my API Call URLs)
public abstract interface ApiModule {
#GET("config")
Call<ConfigResponse> getConfig();
}
ApiServiceGenerator goes like this -
public class ApiServiceGenerator {
public static final String API_BASE_URL = "https://www.example.com/";
private static OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder().addHeader("App-Secret", "some-secret-key").build();
return chain.proceed(newRequest);
}
})
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) // Just For logging
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.build();
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new ArrayAdapterFactory())
.create();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().registerTypeAdapterFactory(new ArrayAdapterFactory()).create()));
public static <S> S createService(Class<S> serviceClass) {
Retrofit retrofit = builder.client(httpClient).build();
return retrofit.create(serviceClass);
}
public static Retrofit retrofit() { // For Error Handing when non-OK response is received from Server
OkHttpClient httpClient = new OkHttpClient.Builder().build();
OkHttpClient client = httpClient;
return builder.client(client).build();
}
}
Finally I resolved my problem..
Its not the problem of the Retrofit library..!!
Actually its my bad. I am opening the fragment twice (which I don't know before answering this question)...
That's why the code inside the fragment is running twice which makes me think as retrofit response is running twice...
In my case, I was using interceptors, inside one of them I called chain.proceed() twice. maybe you should check that too. this will not appear on your log. Use Stetho to check exactly how many times a call is being made.
Don't call any function from retrofit that returns "Response" more than once inside any of your interceptors.
In my case its called twice due to
chain.proceed(request) called twice in same Interceptor
eg
class ErrorInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
chain.proceed(request)
val response = chain.proceed(request) // it called twice in this Interceptor
when (response.code()) {
}
return response
}
}
I'm trying to perform a login action using Retrofit 2.0 using Dagger 2
Here's how I set up Retrofit dependency
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient client) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson)
.client(client)
.baseUrl(application.getUrl())
.build();
return retrofit;
}
Here's the API interface.
interface LoginAPI {
#GET(relative_path)
Call<Boolean> logMe();
}
I have three different base urls users can log into. So I can't set a static url while setting up Retrofit dependency. I created a setUrl() and getUrl() methods on Application class. Upon user login, I set the url onto Application before invoking the API call.
I use lazy injection for retrofit like this
Lazy<Retrofit> retrofit
That way, Dagger injects dependency only when I can call
retrofit.get()
This part works well. I got the url set to retrofit dependency. However, the problem arises when the user types in a wrong base url (say, mywifi.domain.com), understands it's the wrong one and changes it(say to mydata.domain.com). Since Dagger already created the dependency for retrofit, it won't do again.
So I have to reopen the app and type in the correct url.
I read different posts for setting up dynamic urls on Retrofit using Dagger. Nothing really worked out well in my case. Do I miss anything?
Support for this use-case was removed in Retrofit2. The recommendation is to use an OkHttp interceptor instead.
HostSelectionInterceptor made by swankjesse
import java.io.IOException;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
/** An interceptor that allows runtime changes to the URL hostname. */
public final class HostSelectionInterceptor implements Interceptor {
private volatile String host;
public void setHost(String host) {
this.host = host;
}
#Override public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String host = this.host;
if (host != null) {
//HttpUrl newUrl = request.url().newBuilder()
// .host(host)
// .build();
HttpUrl newUrl = HttpUrl.parse(host);
request = request.newBuilder()
.url(newUrl)
.build();
}
return chain.proceed(request);
}
public static void main(String[] args) throws Exception {
HostSelectionInterceptor interceptor = new HostSelectionInterceptor();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
Request request = new Request.Builder()
.url("http://www.coca-cola.com/robots.txt")
.build();
okhttp3.Call call1 = okHttpClient.newCall(request);
okhttp3.Response response1 = call1.execute();
System.out.println("RESPONSE FROM: " + response1.request().url());
System.out.println(response1.body().string());
interceptor.setHost("www.pepsi.com");
okhttp3.Call call2 = okHttpClient.newCall(request);
okhttp3.Response response2 = call2.execute();
System.out.println("RESPONSE FROM: " + response2.request().url());
System.out.println(response2.body().string());
}
}
Or you can either replace your Retrofit instance (and possibly store the instance in a RetrofitHolder in which you can modify the instance itself, and provide the holder through Dagger)...
public class RetrofitHolder {
Retrofit retrofit;
//getter, setter
}
Or re-use your current Retrofit instance and hack the new URL in with reflection, because screw the rules. Retrofit has a baseUrl parameter which is private final, therefore you can access it only with reflection.
Field field = Retrofit.class.getDeclaredField("baseUrl");
field.setAccessible(true);
okhttp3.HttpUrl newHttpUrl = HttpUrl.parse(newUrl);
field.set(retrofit, newHttpUrl);
Retrofit2 library comes with a #Url annotation. You can override baseUrl like this:
API interface:
public interface UserService {
#GET
public Call<ResponseBody> profilePicture(#Url String url);
}
And call the API like this:
Retrofit retrofit = Retrofit.Builder()
.baseUrl("https://your.api.url/");
.build();
UserService service = retrofit.create(UserService.class);
service.profilePicture("https://s3.amazon.com/profile-picture/path");
For more details refer to this link: https://futurestud.io/tutorials/retrofit-2-how-to-use-dynamic-urls-for-requests
This worked for me in Kotlin
class HostSelectionInterceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val host: String = SharedPreferencesManager.getServeIpAddress()
val newUrl = request.url().newBuilder()
.host(host)
.build()
request = request.newBuilder()
.url(newUrl)
.build()
return chain.proceed(request)
}
}
Add the interceptor to OkHttpClient builder
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(HostSelectionInterceptor())
.cache(null)
.build()
This might be late but Retrofit allows you to use dynamic URLs while making the network call itself using #Url annotation.
I am also using Dagger2 to inject the Retrofit instance in my repositories and this solution is working fine for me.
This will use the base url
provided by you while creating the instance of Retrofit.
#GET("/product/123")
fun fetchDataFromNetwork(): Call<Product>
This ignore the base url
and use the url you will be providing this call at run time.
#GET()
fun fetchDataFromNetwork(#Url url : String): Call<Product> //
Thanks to #EpicPandaForce for help. If someone is facing IllegalArgumentException, this is my working code.
public class HostSelectionInterceptor implements Interceptor {
private volatile String host;
public void setHost(String host) {
this.host = HttpUrl.parse(host).host();
}
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String reqUrl = request.url().host();
String host = this.host;
if (host != null) {
HttpUrl newUrl = request.url().newBuilder()
.host(host)
.build();
request = request.newBuilder()
.url(newUrl)
.build();
}
return chain.proceed(request);
}
}
For latest Retrofit library, you can simply use singleton instance and change it with retrofitInstance.newBuilder().baseUrl(newUrl). No need to create another instance.
Dynamic url using Retrofit 2 and Dagger 2
You are able to instantiate new object using un-scoped provide method.
#Provides
LoginAPI provideAPI(Gson gson, OkHttpClient client, BaseUrlHolder baseUrlHolder) {
Retrofit retrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)
.client(client)
.baseUrl(baseUrlHolder.get())
.build();
return retrofit.create(LoginAPI.class);
}
#AppScope
#Provides
BaseUrlHolder provideBaseUrlHolder() {
return new BaseUrlHolder("https://www.default.com")
}
public class BaseUrlHolder {
public String baseUrl;
public BaseUrlHolder(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
}
Now you can change base url via getting baseUrlHolder from the component
App.appComponent.getBaseUrlHolder().set("https://www.changed.com");
this.loginApi = App.appComponent.getLoginApi();
Please look into my workaround for Dagger dynamic URL.
Step1: Create an Interceptor
import android.util.Patterns;
import com.nfs.ascent.mdaas.repo.network.ApiConfig;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class DomainURLInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
String requestUrl = original.url().toString();
String PROTOCOL = "(?i:http|https|rtsp)://";
String newURL = requestUrl.replaceFirst(PROTOCOL, "")
.replaceFirst(Patterns.DOMAIN_NAME.toString(), "");
newURL = validateBackSlash(newURL) ? ApiConfig.BASE_URL.concat(newURL) : newURL.replaceFirst("/", ApiConfig.BASE_URL);
original = original.newBuilder()
.url(newURL)
.build();
return chain.proceed(original);
}
private boolean validateBackSlash(String str) {
if (!str.substring(str.length() - 1).equals("/")) {
return true;
}
return false;
}
}
Step 2:
add your newly created interceptor in your module
#Provides
#Singlton
DomainURLInterceptor getChangeURLInterceptor() {
return new DomainURLInterceptor();
}
step 3:
add interceptor into list of HttpClient interceptors
#Provides
#Singlton
OkHttpClient provideHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(getChangeURLInterceptor())
.readTimeout(ApiConfig.API_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.connectTimeout(ApiConfig.API_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
.build();
}
step 4:
#Provides
#Singlton
Retrofit provideRetrofit() {
return new Retrofit.Builder()
.baseUrl(ApiConfig.BASE_URL) // this is default URl,
.addConverterFactory(provideConverterFactory())
.client(provideHttpClient())
.build();
}
Note: if the user has to change the Base URL from settings, remember to validate the newly created URL with below method:
public final static boolean isValidUrl(CharSequence target) {
if (target == null) {
return false;
} else {
return Patterns.WEB_URL.matcher(target).matches();
}
}