I am creating android application which uses retrofit. I have used spring as rest api. I have used authentication with JWT. I have use two interceptor here RequestInterceptor and ResponseInterceptor. The scenario of calling BASE_URL/hello api with expire JWT is as below
client call /hello with expired accesstoken in header using RequestInterceptor
server check token and response with code 401/403
client check response code and call /refresh using ResponseInterceptor with refreshtoken in header
Server check refreshtoken and response with new accesstoken
now the problem is how to call again /hello. I want this for each request. How can i predict which request has been made last.
Here is the code:
part of the code where /hello is called
btnNext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Call<HelloResponse> call= RetrofitFactoryWithJwt.getRetrofitInstance(getApplicationContext()).helloUser();
call.enqueue(new Callback<HelloResponse>() {
#Override
public void onResponse(Call<HelloResponse> call, Response<HelloResponse> response) {
Log.d(TAG,"after call in enque");
if(response.code()==200)
{
Log.d(TAG,response.body().getSuccess());
}
else
{
Log.d(TAG,"problem in response:"+response.code());
}
}
#Override
public void onFailure(Call<HelloResponse> call, Throwable t) {
Log.d(TAG,"onfailure"+t.getMessage());
}
});
Intent intent = new Intent( getApplicationContext(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
});
RequestInterceptor.java
public class RequestInterceptor implements Interceptor {
Context context;
String TAG="heyrequest";
public RequestInterceptor(Context context)
{
this.context=context;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
//if url is /refresh add refresh token in header instead of accesstoken
if(originalRequest.url().encodedPath().equalsIgnoreCase("/refresh"))
{
SharedPreferences preferences = context.getSharedPreferences("tokens", MODE_PRIVATE);
String refreshvalue=preferences.getString("refreshtoken","");
// rewrite the request
Request newRequest=originalRequest.newBuilder()
.addHeader("Authorization","Bearer "+refreshvalue)
.build();
return chain.proceed(newRequest);
}
//for context we have use requestinterceptor context construction
SharedPreferences preferences = context.getSharedPreferences("tokens", MODE_PRIVATE);
String tokenvalue=preferences.getString("accesstoken","");
// rewrite the request
Request newRequest=originalRequest.newBuilder()
.addHeader("Authorization","Bearer "+tokenvalue)
.build();
return chain.proceed(newRequest);
}
}
ResponseInterceptor.java
public class ResponseInterceptor implements Interceptor {
Context context;
String TAG="heyresponse";
String accesstoken=null;
Response response=null;
public ResponseInterceptor(Context context)
{
this.context=context;
}
#Override
public Response intercept(final Chain chain) throws IOException {
final Request request=chain.request();
response = chain.proceed(request);
if(response.code()==401 || response.code()==403)
{
accesstoken=getNewToken();
}
return chain.proceed(request);
}
public String getNewToken()
{
Call<RefreshResponse> call= RetrofitFactoryWithJwt.getRetrofitInstance(context).refreshToken();
call.enqueue(new Callback<RefreshResponse>() {
#Override
public void onResponse(Call<RefreshResponse> call, retrofit2.Response<RefreshResponse> response1) {
Log.d(TAG,"in refreshtoken call");
if(response1.code()==200)
{
accesstoken=response1.body().getAccesstoken();
Log.d(TAG,accesstoken);
SharedPreferences preferences = context.getSharedPreferences("tokens", MODE_PRIVATE);
preferences.edit().putString("accesstoken", accesstoken).apply();
}
else
{
Log.d(TAG,"problem in response:"+response1.code());
}
}
#Override
public void onFailure(Call<RefreshResponse> call, Throwable t) {
Log.d(TAG,"onfailure:"+t.getMessage());
}
});
return accesstoken;
}
}
I solved this problem by using authenticator for handle the response and interceptor for adding header in request
Related
I'm trying to implement Oauth2 login with Dagger2. Once the access_token gets expired, I have successfully generated new access_token through the refresh_token, but the Authenticator goes on infinite loop once refresh_token is also expired.
This is my Network module, where I defined, Authenticator and Interceptor in OkHttp Client
#Module
public class NetworkModule
{
#Provides
#Singleton
OkHttpClient provideOkHttpClient(TokenAuthenticator tokenAuthenticator, TokenInceptor tokenInceptor, SharedManager sharedManager)
{
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
// adding socket time for read/write/reconnect
httpClient.connectTimeout(30, TimeUnit.SECONDS);
httpClient.writeTimeout(30, TimeUnit.SECONDS);
httpClient.readTimeout(30, TimeUnit.SECONDS);
// setting the accept type of the request to application/json
httpClient.addNetworkInterceptor(new Interceptor()
{
#Override
public Response intercept(Chain chain) throws IOException {
Request.Builder requestBuilder = chain.request().newBuilder();
requestBuilder.header("Accept", "application/json");
return chain.proceed(requestBuilder.build());
}
});
httpClient.addInterceptor(logging).addInterceptor(tokenInceptor);
httpClient.authenticator(tokenAuthenticator);
return httpClient.build();
}
}
#Provides
Retrofit provideRetrofit(OkHttpClient okHttpClient){
return new Retrofit.Builder()
.baseUrl(ApiConstants.API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
#Provides
#Singleton
ApiService provideApiService(Retrofit retrofit, TokenService apiServiceHolder)
{
ApiService apiService = retrofit.create(ApiService.class);
apiServiceHolder.setApiService(apiService);
return apiService;
}
#Provides
#Singleton
public SharedPreferences providePreferences(Application application)
{
return application.getSharedPreferences(Constants.APP_PREFERENCES, Context.MODE_PRIVATE);
}
#Provides
#Singleton
public SharedManager provideSharedManager(SharedPreferences sharedPreferences)
{
return new SharedManager(sharedPreferences);
}
#Provides
#Singleton
public TokenAuthenticator tokenAuthenticator(TokenService tokenService, SharedManager sharedManager)
{
return new TokenAuthenticator(tokenService, sharedManager);
}
#Provides
#Singleton
public TokenInceptor tokenInceptor(SharedManager sharedManager)
{
return new TokenInceptor(sharedManager);
}
#Provides
#Singleton
public TokenService apiServiceHolder()
{
return new TokenService();
}
}
Here's the Interceptor
#Singleton
public class TokenInceptor implements Interceptor
{
SharedManager sharedManager;
#Inject
public TokenInceptor(SharedManager sharedManager)
{
this.sharedManager = sharedManager;
}
#Override
public Response intercept(Chain chain) throws IOException
{
Request request = chain.request();
// we don't need header in login/register so, we remove the header from these api request endpoints
if(request.url().encodedPath().contains("/token/client") && request.method().equalsIgnoreCase("POST"))
{
return chain.proceed(request);
}
// then we add the authenticator to other api requests
HttpUrl url = request.url();
Request.Builder urlBuilder = request.newBuilder().addHeader(ApiConstants.AUTHORIZATION, sharedManager.getBearer()).url(url);
Request apiRequest = urlBuilder.build();
return chain.proceed(apiRequest);
}
}
Here's the Authenticator
#Singleton
public class TokenAuthenticator implements Authenticator
{
private SharedManager sharedManager;
private TokenService tokenService;
#Inject
public TokenAuthenticator(#NonNull TokenService apiServiceHolder, SharedManager sharedManager)
{
this.tokenService = apiServiceHolder;
this.sharedManager = sharedManager;
}
#Nullable
#Override
public Request authenticate(Route route, Response response) throws IOException
{
if(!response.request().header(ApiConstants.AUTHORIZATION).equals(sharedManager.getBearer()))
{
return null;
}
retrofit2.Response<TokenResponse> tokenResponse = tokenService.getApiService().refreshToken(sharedManager.getRefresh()).execute();
TokenResponse responseData = tokenResponse.body();
if(tokenResponse.isSuccessful() && responseData!= null)
{
TokenResponse responseRequest = (TokenResponse) tokenResponse.body();
String new_token = responseRequest.getAccess();
sharedManager.saveAccessToken(new_token);
return response.request().newBuilder().header(ApiConstants.AUTHORIZATION,sharedManager.getBearer()).build();
}
else
{
// As per my assumption, the refresh token might expire here
Log.e("refresh_token","expired");
}
return null;
}
}
Here's the TokenService class
public class TokenService
{
ApiService apiService = null;
#Nullable
public ApiService getApiService() {
return apiService;
}
public void setApiService(ApiService apiService) {
this.apiService = apiService;
}
}
Here's SharedManager class
public class SharedManager
{
private SharedPreferences sharedPreferences;
#Inject
public SharedManager(SharedPreferences sharedPreferences)
{this.sharedPreferences = sharedPreferences;};
public void saveAccessToken(String token)
{
sharedPreferences.edit().putString(ApiConstants.ACCESS_TOKEN, token).commit();
}
public void saveRefreshToken(String token)
{
sharedPreferences.edit().putString(ApiConstants.REFRESH, token).commit();
}
public String getAccessToken()
{
return sharedPreferences.getString(ApiConstants.ACCESS_TOKEN, "");
}
public String getRefresh()
{
return sharedPreferences.getString(ApiConstants.REFRESH, "");
}
public String getBearer()
{
return "Bearer "+getAccessToken();
}
public void clearAll()
{
sharedPreferences.edit().clear().commit();
}
}
Here's ApiService interface
public interface ApiService
{
// client login
#POST("token/client")
#FormUrlEncoded
Call<ResponseBody> loginUser(#Field("email") String email,
#Field("password") String password);
// method for refresh token
#POST("token/refresh")
#FormUrlEncoded
Call<TokenResponse> refreshToken(#Field("refresh") String refresh);
// get agent
#GET("agent")
Call<ResponseBody> getAgentTour();
}
Can anyone trace out the faults in the code here ? The code structure changed while posting in stack.
A standard refresh token grant message will return an error code of invalid_grant when the refresh token finally expires.
{
"error": "invalid_grant",
"error_description": "An optional description message that varies between vendors"
}
At this point you should do two things:
For any in flight API calls, throw an exception with an error code such as 'login_required', that your error handling code can silently ignore
Then perform a login redirect to start a new user session
SAMPLE CODE OF MINE
A something to compare against, I have an AppAuth code sample that you can run and which allows simulation of token expiry events:
Android Code to Handle Invalid Grant
Android Code Sample Blog Post
Of course you would need to translate this behaviour to your own Dagger based coding preferences ...
All the thing working fine but how to call the the user{first_name,last_name,email}
You have to call a retrofit method in this way,
#FormUrlEncoded
#POST("employer/login")
Call<ResponseModelEmplyrLogin> loginEmployer(
#Field("data") String data);
public void loginEmployer(String data, final MyApiCallbackEmplyrLogin<EmployrDataBean> callback) {
ApiInterface apiService = ApiClient.createService();
Call<ResponseModelEmplyrLogin> call = apiService.loginEmployer(data);
call.enqueue(new Callback<ResponseModelEmplyrLogin>() {
#Override
public void onResponse(Call<ResponseModelEmplyrLogin> call, Response<ResponseModelEmplyrLogin> response) {
//Write your code
}
#Override
public void onFailure(Call<ResponseModelEmplyrLogin> call, Throwable t) {
//Write your code
}
});
}
Check out this link https://www.androidtutorialpoint.com/networking/retrofit-android-tutorial/ ...I think this will be helpful
How can I start a login activity inside of a retrofit2 interceptor? Am using dagger2 to inject dependences, is there any best practices?
say like so- Am getting null pointer on LoginMvp.View mview.
private final SharedPreferences preferences;
#Inject LoginMvp.View mview;
private String token;
#Inject
public AuthInterceptor(SharedPreferences preferences) {
this.preferences = preferences;
}
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
Request request = builder.build();
Response response = chain.proceed(request);
if (response.code() == 401) {
//start login activity
login();
}
}
//should call start loginActivity via intent and call finish
public void logout() {
mview.showLoginScreen();
}
You can create static object of application context in Application context as
public class App extends Application {
public static App context;
#Override
public void onCreate() {
super.onCreate();
context = this;
}
}
Then register this class in AndroidManifest as <application android:name="">
Then in your mview.showLoginScreen() method put null check to application context and if it is not null call your login activity as
Intent intent = new Intent(App.context, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TASK);
App.context.startActivity(intent)
i'm having issues getting Retrofit 2.0 to send POST requests to Python-Django.
Here's my Retrofit method.
public void sendNetworkRequest(User user) {
//Cria instância retrofit
final Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://127.0.0.1:8000/")
.addConverterFactory(GsonConverterFactory.create())
.build();
UserService services = retrofit.create(UserService.class);
Call<User> call = services.createAccount(user);
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
Toast.makeText(CadastrarActivity.this, "Você foi cadastrado com sucesso!", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<User> call, Throwable t) {
CheckConnection checkConnection = new CheckConnection();
if (checkConnection.equals(null)){
Toast.makeText(CadastrarActivity.this, "Conecte-se a internet!", Toast.LENGTH_SHORT).show();
}
else {
Log.e("heurys", t.getMessage());
System.out.println(t.getStackTrace());
Toast.makeText(CadastrarActivity.this, "Algo deu errado!", Toast.LENGTH_SHORT).show();
}
}
});
}
Here's my interface method used in the Rest call:
public interface UserService {
#POST("rest/cadastro")
Call<User> createAccount(#Body User user);
}
And here's my traceback error:
04-03 12:58:43.726 18692-18692/com.example.ccyrobuosi.estudos E/heurys: Failed to connect to /127.0.0.1:8000
In advance, my Python code works just fine, i used Postman to test it, and its getting the requests properly.
I am building a Login system for an Android app. I am using OkHttp to connect to my server and get a JSON response.
I have defined a class with the login return data (right now just a true/false response based on whether the user exists in the database), and then written the code to connect to the server, as shown below:
class UserLogin {
boolean status;
public void setStatus(boolean status) {
this.status = status;
}
public boolean getStatus() {
return status;
}
}
public class ClientServerInterface {
OkHttpClient client = new OkHttpClient();
boolean login(Request request) {
final Gson gson = new Gson();
client.newCall(request).enqueue(new Callback() {
UserLogin login;
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
login = gson.fromJson(response.body().charStream(), UserLogin.class);
login.setStatus(login.status);
}
});
// need to return the boolean response (status) here
}
}
The code which passes the Request variable to the login method works perfectly. I want login to return a boolean response so that I can pass that to other methods in other classes.
However, because the UserLogin object is defined in the callback I can't access it in the parent method. I have made a getStatus method but not sure how to use it properly to get the status in the main login method.
The code which passes the Request variable to the login method works
perfectly. I want login to return a boolean response so that I can
pass that to other methods in other classes.
you can't. enqueue executes the code in Async way. You don't know when the callback is invoked. What you could do is to add the Callback as parameter to your login method. E.g.
boolean login(Request request, final Callback callback) {
and either pass it to enqueue,
client.newCall(request).enqueue(callback);
or call the callback manually. E.g.
#Override
public void onResponse(Call call, Response response) throws IOException {
if (callback != null) {
callback.onResponse(call, response);
}
}
in both cases the caller of login will receive the callback on the provided object and, accordingly to the content it receives, can decide wha actions undertake
You can do this using a SynchronousQueue:
final SynchronousQueue<Boolean> queue = new SynchronousQueue<>();
client.newCall(request).enqueue(new Callback() {
UserLogin login;
#Override
public void onFailure(Call call, IOException e) {
queue.put(false);
}
#Override
public void onResponse(Call call, Response response) throws IOException {
queue.put(true);
}
});
return queue.take();
Add loginStatus variable to class like below and one more to indicate login operation completion.
public class ClientServerInterface {
OkHttpClient client = new OkHttpClient();
private boolean loginStatus = false;
private boolean isLoginOperationDone = false;
boolean login(Request request) {
final Gson gson = new Gson();
client.newCall(request).enqueue(new Callback() {
UserLogin login;
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
login = gson.fromJson(response.body().charStream(), UserLogin.class);
loginStatus = login.setStatus(login.status);
isLoginOperationDone = true;
}
});
// need to return the boolean response (status) here
while( !isLoginOperationDone )
{
//not to do anything.
}
return loginStatus;
}
}
Note that this might be a little hacky but will do solve your problem.
The way to go is with AsyncTask. Override the doInBackground method to perform the http requests and get the result by overriding the onPostExecute method.
Read more here: http://developer.android.com/reference/android/os/AsyncTask.html
An even better way to go is to run a background Service for all your API calls.