Scenario: I am using OkHttp / Retrofit to access a web service: multiple HTTP requests are sent out at the same time. At some point the auth token expires, and multiple requests will get a 401 response.
Issue: In my first implementation I use an interceptor (here simplified) and each thread tries to refresh the token. This leads to a mess.
public class SignedRequestInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 1. sign this request
request = request.newBuilder()
.header(AUTH_HEADER_KEY, BEARER_HEADER_VALUE + token)
.build();
// 2. proceed with the request
Response response = chain.proceed(request);
// 3. check the response: have we got a 401?
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// ... try to refresh the token
newToken = mAuthService.refreshAccessToken(..);
// sign the request with the new token and proceed
Request newRequest = request.newBuilder()
.removeHeader(AUTH_HEADER_KEY)
.addHeader(AUTH_HEADER_KEY, BEARER_HEADER_VALUE + newToken.getAccessToken())
.build();
// return the outcome of the newly signed request
response = chain.proceed(newRequest);
}
return response;
}
}
Desired solution: All threads should wait for one single token refresh: the first failing request triggers the refresh, and together with the other requests waits for the new token.
What is a good way to proceed about this? Can some built-in features of OkHttp (like the Authenticator) be of help? Thank you for any hint.
I had the same problem and I managed to solve it using a ReentrantLock.
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class RefreshTokenInterceptor implements Interceptor {
private Lock lock = new ReentrantLock();
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// first thread will acquire the lock and start the refresh token
if (lock.tryLock()) {
Timber.i("refresh token thread holds the lock");
try {
// this sync call will refresh the token and save it for
// later use (e.g. sharedPreferences)
authenticationService.refreshTokenSync();
Request newRequest = recreateRequestWithNewAccessToken(chain);
return chain.proceed(newRequest);
} catch (ServiceException exception) {
// depending on what you need to do you can logout the user at this
// point or throw an exception and handle it in your onFailure callback
return response;
} finally {
Timber.i("refresh token finished. release lock");
lock.unlock();
}
} else {
Timber.i("wait for token to be refreshed");
lock.lock(); // this will block the thread until the thread that is refreshing
// the token will call .unlock() method
lock.unlock();
Timber.i("token refreshed. retry request");
Request newRequest = recreateRequestWithNewAccessToken(chain);
return chain.proceed(newRequest);
}
} else {
return response;
}
}
private Request recreateRequestWithNewAccessToken(Chain chain) {
String freshAccessToken = sharedPreferences.getAccessToken();
Timber.d("[freshAccessToken] %s", freshAccessToken);
return chain.request().newBuilder()
.header("access_token", freshAccessToken)
.build();
}
}
The main advantage of using this solution is that you can write an unit test using mockito and test it. You will have to enable Mockito Incubating feature for mocking final classes (response from okhttp). Read more about here.
The test looks something like this:
#RunWith(MockitoJUnitRunner.class)
public class RefreshTokenInterceptorTest {
private static final String FRESH_ACCESS_TOKEN = "fresh_access_token";
#Mock
AuthenticationService authenticationService;
#Mock
RefreshTokenStorage refreshTokenStorage;
#Mock
Interceptor.Chain chain;
#BeforeClass
public static void setup() {
Timber.plant(new Timber.DebugTree() {
#Override
protected void log(int priority, String tag, String message, Throwable t) {
System.out.println(Thread.currentThread() + " " + message);
}
});
}
#Test
public void refreshTokenInterceptor_works_as_expected() throws IOException, InterruptedException {
Response unauthorizedResponse = createUnauthorizedResponse();
when(chain.proceed((Request) any())).thenReturn(unauthorizedResponse);
when(authenticationService.refreshTokenSync()).thenAnswer(new Answer<Boolean>() {
#Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
//refresh token takes some time
Thread.sleep(10);
return true;
}
});
when(refreshTokenStorage.getAccessToken()).thenReturn(FRESH_ACCESS_TOKEN);
Request fakeRequest = createFakeRequest();
when(chain.request()).thenReturn(fakeRequest);
final Interceptor interceptor = new RefreshTokenInterceptor(authenticationService, refreshTokenStorage);
Timber.d("5 requests try to refresh token at the same time");
final CountDownLatch countDownLatch5 = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
#Override
public void run() {
try {
interceptor.intercept(chain);
countDownLatch5.countDown();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
countDownLatch5.await();
verify(authenticationService, times(1)).refreshTokenSync();
Timber.d("next time another 3 threads try to refresh the token at the same time");
final CountDownLatch countDownLatch3 = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
#Override
public void run() {
try {
interceptor.intercept(chain);
countDownLatch3.countDown();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
countDownLatch3.await();
verify(authenticationService, times(2)).refreshTokenSync();
Timber.d("1 thread tries to refresh the token");
interceptor.intercept(chain);
verify(authenticationService, times(3)).refreshTokenSync();
}
private Response createUnauthorizedResponse() throws IOException {
Response response = mock(Response.class);
when(response.code()).thenReturn(401);
return response;
}
private Request createFakeRequest() {
Request request = mock(Request.class);
Request.Builder fakeBuilder = createFakeBuilder();
when(request.newBuilder()).thenReturn(fakeBuilder);
return request;
}
private Request.Builder createFakeBuilder() {
Request.Builder mockBuilder = mock(Request.Builder.class);
when(mockBuilder.header("access_token", FRESH_ACCESS_TOKEN)).thenReturn(mockBuilder);
return mockBuilder;
}
}
You should not use interceptors or implement the retry logic yourself as this leads to a maze of recursive issues.
Instead implement the okhttp's Authenticator which is provided specifically to solve this problem:
okHttpClient.setAuthenticator(...);
Thanks for your answers - they led me to the solution. I ended up using a ConditionVariable lock and an AtomicBoolean. Here's how you can achieve this: read through the comments.
/**
* This class has two tasks:
* 1) sign requests with the auth token, when available
* 2) try to refresh a new token
*/
public class SignedRequestInterceptor implements Interceptor {
// these two static variables serve for the pattern to refresh a token
private final static ConditionVariable LOCK = new ConditionVariable(true);
private static final AtomicBoolean mIsRefreshing = new AtomicBoolean(false);
...
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
// 1. sign this request
....
// 2. proceed with the request
Response response = chain.proceed(request);
// 3. check the response: have we got a 401?
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
if (!TextUtils.isEmpty(token)) {
/*
* Because we send out multiple HTTP requests in parallel, they might all list a 401 at the same time.
* Only one of them should refresh the token, because otherwise we'd refresh the same token multiple times
* and that is bad. Therefore we have these two static objects, a ConditionVariable and a boolean. The
* first thread that gets here closes the ConditionVariable and changes the boolean flag.
*/
if (mIsRefreshing.compareAndSet(false, true)) {
LOCK.close();
// we're the first here. let's refresh this token.
// it looks like our token isn't valid anymore.
mAccountManager.invalidateAuthToken(AuthConsts.ACCOUNT_TYPE, token);
// do we have an access token to refresh?
String refreshToken = mAccountManager.getUserData(account, HorshaAuthenticator.KEY_REFRESH_TOKEN);
if (!TextUtils.isEmpty(refreshToken)) {
.... // refresh token
}
LOCK.open();
mIsRefreshing.set(false);
} else {
// Another thread is refreshing the token for us, let's wait for it.
boolean conditionOpened = LOCK.block(REFRESH_WAIT_TIMEOUT);
// If the next check is false, it means that the timeout expired, that is - the refresh
// stuff has failed. The thread in charge of refreshing the token has taken care of
// redirecting the user to the login activity.
if (conditionOpened) {
// another thread has refreshed this for us! thanks!
....
// sign the request with the new token and proceed
// return the outcome of the newly signed request
response = chain.proceed(newRequest);
}
}
}
}
// check if still unauthorized (i.e. refresh failed)
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
... // clean your access token and prompt user for login again.
}
// returning the response to the original request
return response;
}
}
If you wan't your threads to bock while the first one refresh the token you can use a synchronized block.
private final static Object lock = new Object();
private static long lastRefresh;
...
synchronized(lock){ // lock all thread untill token is refreshed
// only the first thread does the w refresh
if(System.currentTimeMillis()-lastRefresh>600000){
token = refreshToken();
lastRefresh=System.currentTimeMillis();
}
}
Here 600000 (10 min) is arbitrary this number should be big enouth to prevent muliple refresh call and smaller than your token expiration time so that you call the refresh when the token expires.
Edited for thread safety
Havent looked at OkHttp or retrofit but how about having a static flag that is set as soon as a token fails and check for that flag before you request a new token?
private static AtomicBoolean requestingToken = new AtomicBoolean(false);
//.....
if (requestingToken.get() == false)
{
requestingToken.set(true);
//.... request a new token
}
Related
Edited with more information.
I am getting error code 5 which I believe is 'Developer Error'. The mDebugMessage in the BillingResult is Invalid Token. (I have checked the name and I am sure it is correct.)
I am calling consumeAsync("noadverts"); where "noadverts" is the name of my in app product.
When I check my purchases, it records the following:
{"orderId":"GPA.1111-1111-1111-1111",
"packageName":"myname.com.myname",
"productId":"noadverts",
"purchaseTime":1565188785096,
"purchaseState":0,
"purchaseToken":"edited for length",
"acknowledged":true}
Unusually, when I call:
int val1 = purchase.getPurchaseState();
then val1 is 1 and not 0 as it suggested in the purchase JSON.
I am using much of the TestDrive example, but without the BillingUpdatesListener.
public void consumeAsync(final String purchaseToken) {
// If we've already scheduled to consume this token - no action is needed (this could happen
// if you received the token when querying purchases inside onReceive() and later from
// onActivityResult()
if (mTokensToBeConsumed == null) {
mTokensToBeConsumed = new HashSet<>();
} else if (mTokensToBeConsumed.contains(purchaseToken)) {
//Log.i(TAG, "Token was already scheduled to be consumed - skipping...");
return;
}
mTokensToBeConsumed.add(purchaseToken);
// Generating Consume Response listener
final ConsumeResponseListener onConsumeListener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(BillingResult response, String purchaseToken) {
#BillingClient.BillingResponseCode int responseCode = response.getResponseCode();
// THIS IS WHERE I GET ERROR CODE 5
}
};
// Creating a runnable from the request to use it inside our connection retry policy below
Runnable consumeRequest = new Runnable() {
#Override
public void run() {
// Consume the purchase async
//mBillingClient.consumeAsync(purchaseToken, onConsumeListener);
ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build();
// Consume the purchase async
mBillingClient.consumeAsync(consumeParams, onConsumeListener);
}
};
executeServiceRequest(consumeRequest);
}
Any ideas what might be the issue?
If I try and purchase "noadverts" again I get the error code Item is already owned.
I am using retrofit to pass login and register api in android. But I am getting response as 409 in return. I am not getting data from api. Retrofit 2 is used here
SignUpApi signupapi = Api_Config.getInstance3().getApiBuilder().create(SignUpApi.class);
Call<SignUpApi.ResponseSignUp> call = signupapi.POSTDATA(UserName.getText().toString().trim(),
Email.getText().toString().trim(),
Password.getText().toString().trim(),
Sex.getText().toString().trim(),
Mobile.getText().toString().trim());
call.enqueue(new Callback<SignUpApi.ResponseSignUp>() {
#Override
public void onResponse(Call<SignUpApi.ResponseSignUp> call, Response<SignUpApi.ResponseSignUp> response) {
CustomProgressDialog.getInstance().dismiss();
if (response.isSuccessful()){
Log.e("Status is",response.body().getStatus().toString());
if (response.body().getStatus() == 200){
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.SuccessfullyRegistered);
CommonFunctions.getInstance().FinishActivityWithDelay(SignInActivity.this);
}else if (response.body().getStatus() == 409){
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.YouAreAlreadyRegistered);
}else{
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,response.body().getMsg());
}
} else {
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.SomethingWentWrong);
}
}
#Override
public void onFailure(Call<SignUpApi.ResponseSignUp> call, Throwable t) {
CustomProgressDialog.getInstance().dismiss();
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,t.getMessage());
}
});
}
Below is my API configuration
public static Api_Config getInstance3()
{
if (ourInstance == null){
synchronized (Api_Config.class){
if ( ourInstance == null )
ourInstance = new Api_Config();
}
}
ourInstance.config3();
return ourInstance;
}
private void config3() {
Gson gson = new GsonBuilder()
.setLenient()
.create();
String BASE_URL3 = LOGIN_AND_SIGNUP;
mRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL3)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
Below is my Api Class
public interface SignUpApi {
#FormUrlEncoded
#POST("register.php")
Call<ResponseSignUp> POSTDATA(#Field("user_name")String username,
#Field("user_email")String email,
#Field("user_password")String password,
#Field("user_gender")String sex,
#Field("user_mobile")String mobile
);
public class ResponseSignUp
{
#SerializedName("status")
#Expose
private Integer status;
#SerializedName("msg")
#Expose
private String msg;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
I am newbee to android and really confused why my code is not working. Looking for help. Thanks in Advance
Note that if your response was successfully it means you got a successful code (200...300). However, if you get a response 401, 409.. it means you got an error, then your response was not successfully. Put the error handle outside the response.isSuccessful() condition.
if (response.isSuccessful()){
//Handle success response here
Log.e("Status is",response.body().getStatus().toString());
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.SuccessfullyRegistered);
CommonFunctions.getInstance().FinishActivityWithDelay(SignInActivity.this);
} else {
// Handle error response here, 401, 409...
if (response.body().getStatus() == 409){
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,Constants.YouAreAlreadyRegistered);
}else{
CommonFunctions.getInstance().ShowSnackBar(SignInActivity.this,response.body().getMsg());
}
}
You can check this in the retrofit2 files.
/** Returns true if {#link #code()} is in the range [200..300). */
public boolean isSuccessful() {
return rawResponse.isSuccessful();
}
EDIT
Depper explanation
I'll try to explain better. When you call retrofit using enqueue method like this: call.enqueue() you expect to get a Response or Failure from the server: onResponse() means you got a response and onFailure() means you failed to connect to the server, it could mean the server is broken or there is no internet connection.
If you got a onResponse() from the server it does not mean it was successful, it just means you got a response, therefore you need to check if this response was successful or not by using this condition
if (response.isSuccessful){
}
What is a successful response?
If you end up inside this condition response.isSuccessful it already means you got a successful response and this is a response with code between 200 and 300.
However, if you want to check if you got a 409 code. 409 code means that it was a unsuccessful response, then you need to check this outside the success condition.
if (response.isSuccessful){
// You got a successful response, the code is from 200 to 300.
} else {
// You got a unsuccessful response, handle the code 401, 405, 409 here.
}
I wonder if it's possible to send push notifications to android mobile devices whenever Firebase gets added a new child on specific entity.
For example, let's say there's an entity on Firebase called Tasks. Whenever a new task is added to that firebase collection the "child_added" event is fired and then, in some way, a push notification is sent to a mobile client.
The trigger is the child_added event. However, I'm not sure if is feasible sending push notification right from Firebase events.
You can make a very simple node.js server or a java servlet (based on your language preferences) then using firebase server sdk you can add childEventListener. When value changes you can use FCM to send push notifications using http protocol. I am using this in my app and it is very feasable and reliable.
Note: If you are using this flow for an android app then using java server sdk will be beneficial as it is almost similar to what you have on android.
EDIT: After getting some spotlight on this answer I thought to share some more info regarding same.
//example node.js server as seen on this official firebase blog
var firebase = require('firebase');
var request = require('request');
var API_KEY = "..."; // Your Firebase Cloud Server API key
firebase.initializeApp({
serviceAccount: ".json",
databaseURL: "https://.firebaseio.com/"
});
ref = firebase.database().ref();
function listenForNotificationRequests() {
var requests = ref.child('notificationRequests');
ref.on('child_added', function(requestSnapshot) {
var request = requestSnapshot.val();
sendNotificationToUser(
request.username,
request.message,
function() {
request.ref().remove();
}
);
}, function(error) {
console.error(error);
});
};
function sendNotificationToUser(username, message, onSuccess) {
request({
url: 'https://fcm.googleapis.com/fcm/send',
method: 'POST',
headers: {
'Content-Type' :' application/json',
'Authorization': 'key='+API_KEY
},
body: JSON.stringify({
notification: {
title: message
},
to : '/topics/user_'+username
})
}, function(error, response, body) {
if (error) { console.error(error); }
else if (response.statusCode >= 400) {
console.error('HTTP Error: '+response.statusCode+' - '+response.statusMessage);
}
else {
onSuccess();
}
});
}
// start listening
listenForNotificationRequests();
//example test java servlet which I made just to demonstrate this use case
#WebServlet("/TestServlet")
public class MainServlet extends HttpServlet {
* #see HttpServlet#HttpServlet()
*/
public MainServlet() {
super();
}
/**
* #see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Get context and then relative path to saved json config file from
// firebase
ServletContext context = getServletContext();
String fullPath = context.getRealPath(FILE_PATH_FOR_JSON_SERVER_AUTH);
// Check if we actually got a file from above path
if (fullPath != null) {
} else {
}
// Setup connection to firebase database here
FirebaseOptions options = new FirebaseOptions.Builder().setServiceAccount(new FileInputStream(fullPath))
.setDatabaseUrl(FIREBASE_DATABSE_URL).build();
// Check to make sure we don't initialize firebase app each time webpage
// is refreshed
if (!exists) {
// If firebase app doesn't exist then initialize it here and set
// exists to true
FirebaseApp.initializeApp(options);
exists = true;
}
// Call this to begin listening *notify* node in firebase database for notifications
addNotificationListener(request, response);
}
/**
* #see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Build apache httpclient POST request
HttpClient client = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(ENDPOINT_URL);
//Get the required id stored in lastMsgId tree map here
if (!(chatLogs.getLastMsgIdTreeMap().isEmpty())) {
sendToID = chatLogs.getLastMsgIdTreeMap().firstKey();
lstmsg = chatLogs.getLastMsgIdTreeMap().get(sendToID);
}
//Set up a unique id with concatenating sendToID and lstmsg
uniqueID = sendToID + lstmsg;
//Set up a previous id to check with unique id. To avoid instant duplicate notifications
previousID = fcmHelper.getPreviousid();
// Check uniqueId and PreviousID beforeSending
if (!(uniqueID.equals(previousID))) {
fcmHelper.setPreviousid(uniqueID);
//Check if device token and user Id hashmap is not null
if (!(userLogs.getUserIdAndFcmTokenHashMap().isEmpty())) {
//Get the device token of sendTo Id here
deviceToken = userLogs.getUserIdAndFcmTokenHashMap().get(sendToID);
// Create JSON object for downstream data/notification
JSONObject mainNotificationJsonObj = new JSONObject();
JSONObject outerBaseJsonObj = new JSONObject();
try {
// Notification payload has 'title' and 'body' key
mainNotificationJsonObj.put(TITLE, NEW_MESSAGE_RECEIVED);
mainNotificationJsonObj.put(BODY, lstmsg);
mainNotificationJsonObj.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_TYPE_DEFAULT);
//mainNotificationJsonObj.put(TAG, fcmHelper.getFcmTagId());
System.out.println("This is sentBy id =" + fcmHelper.getFcmTagId());
// This will be used in case of both 'notification' or 'data' payload
outerBaseJsonObj.put(TO, deviceToken);
// Set priority of notification. For instant chat setting
// high will
// wake device from idle state - HIGH BATTERY DRAIN
outerBaseJsonObj.put(PRIORITY_KEY, PRIORITY_HIGH);
// Specify required payload key here either 'data' or
// 'notification'. We can even use both payloads in single
// message
outerBaseJsonObj.put(NOTIFICATION, mainNotificationJsonObj);
} catch (JSONException e) {
e.printStackTrace();
}
// Setup http entity with json data and 'Content-Type' header
StringEntity requestEntity = new StringEntity(outerBaseJsonObj.toString(),
ContentType.APPLICATION_JSON);
// Setup required Authorization header
post.setHeader(AUTHORIZATION, FIREBASE_SERVER_KEY);
// Pass setup entity to post request here
post.setEntity(requestEntity);
// Execute apache http client post response
HttpResponse fcmResponse = client.execute(post);
// Get status code from FCM server to debug error and success
System.out.println(RESPONSE_CODE + fcmResponse.getStatusLine().getStatusCode());
// Get response entity from FCM server and read throw lines
BufferedReader rd = new BufferedReader(new InputStreamReader(fcmResponse.getEntity().getContent()));
StringBuffer result = new StringBuffer();
String line = "";
while ((line = rd.readLine()) != null) {
result.append(line);
}
if (response != null) {
// Print out the response to webpage
PrintWriter out;
out = response.getWriter();
out.println(result);
System.out.println("This is Result - " + result);
}
} else {
//Abort this process if conditions not met
post.abort();
System.out.println(THIS_MSG_ALREADY_SENT);
}
}
}
/*
* This is the main method to be called to setup notifications listener on server startup
*/
private void addNotificationListener(HttpServletRequest request, HttpServletResponse response) {
//Initialize Value event listener
lastMsgListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot arg0) {
// Clear idLastMessagerecivedhash map if not null
if (lastMsgIdTreeMap != null) {
lastMsgIdTreeMap.clear();
}
//Get lastmsg to be sent as notification here
lstmsg = (String) arg0.child(LAST_MESSAGE).getValue();
//Get sendToID here
String sendToID = (String) arg0.child(SEND_TO).getValue();
//Get Sent by ID here
sentBy = (String) arg0.child(SENT_BY).getValue();
//Set fcmTag ID here
fcmHelper.setFcmTagId(sentBy);
//Check if lstmsg is not null
if (lstmsg != null) {
// Create lastmsgTimestampHashMap here
lastMsgIdTreeMap.put(sendToID, lstmsg);
}
//Check for null again
if (lastMsgIdTreeMap != null) {
chatLogs.setLastMsgIdTreeMap(lastMsgIdTreeMap);
}
try {
doPost(request, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onCancelled(DatabaseError arg0) {
}
};
//Set up database reference to notify node here
messageRef = FirebaseDatabase.getInstance().getReference().child(NOTIFY);
//Add value listener to database reference here
messageRef.addValueEventListener(lastMsgListener);
}
"Java servlet is just a personal test. Some parts have been edited or removed to only give an idea about it's setup this is in no way production ready servlet please don't just copy - paste. I encourage you to understand and build your own."
Edit: you should take a look at Firebase Cloud Functions, which let you do that without having to create a Node.js server
I'm trying to use Retrofit (2.0.0-beta3), but when using an Authenticator to add a token, I can't seem to get the data from the synchronous call. Our logging on the back-end just shows a lot of login attempts, but I can't get the data from the body to actually add to the header.
public static class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Route route, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
UserService userService = createService(UserService.class);
Call<Session> call = userService.emailLogin(new Credentials("handle", "pass"));
// This call is made correctly, as it shows up on the back-end.
Session body = call.execute().body();
// This line is never hit.
Logger.d("Session token: " + body.token);
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header("Auth-Token", body.token)
.build();
}
}
I'm not exactly too sure on why it's not even printing anything out. Any tips on how to solve this issue would be greatly appreciated, thanks for taking the time to help.
These are the sources I've been reading on how to implement Retrofit.
Using Authenticator:
https://stackoverflow.com/a/31624433/3106174
https://github.com/square/okhttp/wiki/Recipes#handling-authentication
Making synchronous calls with Retrofit 2:
https://futurestud.io/blog/retrofit-synchronous-and-asynchronous-requests
I managed to get a decent solution using the TokenAuthenticator and an Interceptor and thought I'd share the idea as it may help some others.
Adding the 'TokenInterceptor' class that handles adding the token to the header is the token exists, and the 'TokenAuthenticator' class handles the case when there is no token, and we need to generate one.
I'm sure there are some better ways to implement this, but it's a good starting point I think.
public static class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate( Route route, Response response) throws IOException {
...
Session body = call.execute().body();
Logger.d("Session token: " + body.token);
// Storing the token somewhere.
session.token = body.token;
...
}
private static class TokenInterceptor implements Interceptor {
#Override
public Response intercept( Chain chain ) throws IOException {
Request originalRequest = chain.request();
// Nothing to add to intercepted request if:
// a) Authorization value is empty because user is not logged in yet
// b) There is already a header with updated Authorization value
if (authorizationTokenIsEmpty() || alreadyHasAuthorizationHeader(originalRequest)) {
return chain.proceed(originalRequest);
}
// Add authorization header with updated authorization value to intercepted request
Request authorisedRequest = originalRequest.newBuilder()
.header("Auth-Token", session.token )
.build();
return chain.proceed(authorisedRequest);
}
}
Source:
http://lgvalle.xyz/2015/07/27/okhttp-authentication/
I have similar authenticator and it works with 2.0.0-beta2.
If you get lots of login attempts from you Authenticator, I suggest make sure that when you make the synchronous call, you are not using Authenticator with that call.
That could end up in loop, if also your "emailLogin" fails.
Also I would recommend adding loggingInterceptor to see all trafic to server: Logging with Retrofit 2
I know it's a late answer but for anyone still wondering how to Add / Refresh token with Retrofit 2 Authenticator, here is a working solution:
Note: preferenceHelper is your Preference Manager class where you set/get your shared preferences.
public class AuthenticationHelper implements Authenticator {
private static final String HEADER_AUTHORIZATION = "Authorization";
private static final int REFRESH_TOKEN_FAIL = 403;
private Context context;
AuthenticationHelper(#ApplicationContext Context context) {
this.context = context;
}
#Override
public Request authenticate(#NonNull Route route, #NonNull Response response) throws IOException {
// We need to have a token in order to refresh it.
String token = preferencesHelper.getAccessToken();
if (token == null)
return null;
synchronized (this) {
String newToken = preferencesHelper.getAccessToken();
if (newToken == null)
return null;
// Check if the request made was previously made as an authenticated request.
if (response.request().header(HEADER_AUTHORIZATION) != null) {
// If the token has changed since the request was made, use the new token.
if (!newToken.equals(token)) {
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + newToken)
.build();
}
JsonObject refreshObject = new JsonObject();
refreshObject.addProperty("refreshToken", preferencesHelper.getRefreshToken());
retrofit2.Response<UserToken> tokenResponse = apiService.refreshToken(refreshObject).execute();
if (tokenResponse.isSuccessful()) {
UserToken userToken = tokenResponse.body();
if (userToken == null)
return null;
preferencesHelper.saveAccessToken(userToken.getToken());
preferencesHelper.saveRefreshToken(userToken.getRefreshToken());
// Retry the request with the new token.
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + userToken.getToken())
.build();
} else {
if (tokenResponse.code() == REFRESH_TOKEN_FAIL) {
logoutUser();
}
}
}
}
return null;
}
private void logoutUser() {
// logout user
}
}
Also note:
preferenceHelper and apiService needs to be provided in some way.
This is not an example that will work for all systems and api's but an example in how adding and refreshing the token should be done using Retrofit 2 Authenticator
I'm running an application with SignalR 2.2.0 on server side and signalr-java-client (self compiled, last GitHub version) on Android as client.
Currently, there are 4 clients connected to my hub. From time to time, it happens, that all 4 clients simultaneously receive the HTTP status 400 with the message "The connection id is in the incorrect format" (the clients were connected before). I analyzed this multiple times and am not able to find any information/pattern when or why this happens.
The connecten is secured via JWT, the token is definitely valid. When retrieving a new token, the connection is stopped and started again. Apart from this, it is very unlikely that the error is device-related, because the error is thrown at all 4 clients the same time.
I know, this error can occur when the client's Identity changes, but an Identity change for 4 clients the same time seems very unlikely to me.
This is the server-code used for authentication (Deepak asked).
The following method gets called in my Startup.cs:
public static void ConfigureOAuth(IAppBuilder app, string audienceID, string sharedSecret)
{
byte[] secret = TextEncodings.Base64Url.Decode(sharedSecret);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
Provider = new MyOAuthBearerAuthenticationProvider(),
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceID },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(Issuer, secret)
}
});
}
Here's the code of MyOAuthBearerAuthenticationProvider class:
class MyOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
/// <summary>
/// Get's a JWT from querysting and puts it to context
/// </summary>
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context.Token == null)
{
string value = context.Request.Query.Get("auth_token");
if (!string.IsNullOrEmpty(value)) //token from queryString
{
context.Token = value;
}
}
return Task.FromResult<object>(null);
}
}
I have to retrieve the token from query string, because additionally to the java-client, a javascript client is used, which is not able to set headers.
Lastly, I secure my hub and some of it's methods with the Authorization attribute:
[Authorize(Roles = "MyExampleRole")]
This is the client-code for connection:
public boolean connect(String url, String token) {
if (connected) {
return true;
}
try {
this.hubConnection = new HubConnection(url, "auth_token=" + token, true, logger);
this.hubProxy = hubConnection.createHubProxy("MyHub");
this.hubProxy.subscribe(this.signalRMethodProvider);
this.hubConnection.stateChanged(stateChangedCallback);
SignalRFuture<Void> awaitConnection = this.hubConnection.start();
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
return true;
}
catch (InterruptedException | TimeoutException | ExecutionException e) {
log.error("connect", e);
return false;
}
}
Does anybody have an Idea, how to fix this problem or where I may receive further information?
Thank you very much
-Lukas
seems fine...
possible alteration you can do is change
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
to
awaitConnection.done(new Action<Void>() {
#Override
public void run(Void obj) throws Exception {
Log.d(TAG, "Hub Connected");
}
}).onError(new ErrorCallback() {
#Override
public void onError(Throwable error) {
error.printStackTrace();
Log.d(TAG, "SignalRServiceHub Cancelled");
}
}).onCancelled(new Runnable() {
#Override
public void run() {
Log.d(TAG, "SignalRServiceHub Cancelled");
}
});