I am quite new to RxJava and I am tryign to implement and OAuth2 flow with refresh tokens in my Android/Kotlin app.
I got a bit stuck with refreshing my access tokens inside an interceptor (I append access token for each API request inside an OAuthAuthenticator). The problem is that I would like to wait with populating the request until the token request is completed.
Could someone hint me how to achieve this?
This is my authenticator class:
class OAuthAuthenticator(
private val authStateManager: AuthStateManager,
private val authRepository: AuthRepository): Authenticator {
override fun authenticate(route: Route?, response: Response?): Request? {
// handle invalid token
if (response != null && response.code() == 401) {
println("Invalid access token")
authRepository.refreshToken()
.subscribe { _ ->
println("Token refreshed")
}
}
// this should happen after the token request is completed since it would fail again otherwise
return response?.request()!!
.newBuilder()
.header("Authorization", "Bearer " + authStateManager.current.accessToken)
.build()
}
}
You are making an asyncronous call inside a synchronous method. That won't work. You will have to put your code that you want to be executed after token request has been completed inside the rx chain.
class OAuthAuthenticator(
private val authStateManager: AuthStateManager,
private val authRepository: AuthRepository) : Authenticator {
override fun authenticate(route: Route?, response: Response?): Single<Request> {
// handle invalid token
if (response != null && response.code() == 401) {
return Single.error(InvalidAccessTokenException("Invalid access token"))
}
return Single.fromCallable { authRepository.refreshAccessToken() }
.subscribeOn(Schedulers.computation())
.map {
return#map response?.request()!!
.newBuilder()
.header("Authorization", "Bearer " +
authStateManager.current.accessToken)
.build()
}
}
}
And then you can call it like this:
val authenticator = OAuthAuthenticator(authStateManager, authRepository)
val disposable = authenticator.authenticate(route, response)
.subscribe({ _ ->
println("Token refreshed")
}, { error ->
if (error is InvalidAccessTokenException) {
println("Invalid access token")
} else {
println("Could not refresh token. Signing out...")
authStateManager.signOut()
}
})
You can use blockingGet() to make the Single synchronous. But that's not advised. But since you create the Single locally with
Single.fromCallable { authRepository.refreshAccessToken() }
you could just call the appropriate method directly without wrapping it with Rx.
if (response != null && response.code() == 401) {
println("Invalid access token")
try {
authRepository.refreshAccessToken()
println("Token refreshed")
} catch(error: Throwable) {
println("Could not refresh token. Signing out...")
authStateManager.signOut()
return null
}
}
Related
I have a list of URLs that I need to call and if I got success response for all of them, I can continue.
I can simply do it with RxJava and Retrofit like:
#PUT
fun uploadFile(#Url url: String, #Body file: RequestBody): Single<Response<Void>>
Observable.fromIterable(uploadUrls)
.flatMapSingle {
val requestBody = InputStreamRequestBody(contentResolver, uri)
upsellServiceApi.uploadFile(url, requestBody)
}
.toList()
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.subscribe(
{ responses ->
if (responses.all { it.isSuccessful }) {
// continue
} else {
//error
}
},
{
// error
}
Now I need to do the same thing without retrofit and by only using okhttpclient. How should I do it?
You could use a library like https://github.com/liujingxing/rxhttp to keep using RxJava. Or implement it yourself Using RxJava and Okhttp
If you don't want to use RxJava, then just enqueue and handle callback methods yourself.
Solved it
Observable.fromIterable(uploadUrls)
.map {
val request = Request.Builder().url(url).put(InputStreamRequestBody(contentResolver!!, uri)).build()
okHttpClient.newCall(request).execute()
}
.collect(
{
mutableListOf<Response>()
},
{
list: MutableList<Response>, response: Response -> list.add(response)
}
)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.subscribe(
{ responses ->
if (responses.all { it.isSuccessful }) {
// continue
} else {
// error
}
},
{
// error
}
).also { compositeDisposable.add(it) }
I have a kotlin script that gets a token from a server. It then uses that token to perform another request. But, I can't seem to use the token in the next request because the token has a local scope.
This is my code:
class requests{
private val client = OkHttpClient()
private val coroutineScope = CoroutineScope(Dispatchers.Default)
fun run() {
coroutineScope.launch {
val postBody = "encodedinfo".trimMargin()
val request = Request.Builder()
.url("https://example.com")
.post(postBody.toRequestBody(MEDIA_TYPE_MARKDOWN))
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val token = response.body!!.string()
}
}
}
companion object {
val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
}
}
With this code I can't use the token variable outside of the request code block. This block of code:
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val token = response.body!!.string()
}
This is the first request that gets the token variable and I need the token so that I can use it in another function so that I can perform the second request and load the users info.
use is a generic stdlib function used on Closeable. Here is its signature:
inline fun <T : Closeable?, R> T.use(block: (T) -> R): R
As you can see, it returns whatever value the given lambda returns. This result is the last expression in the lambda block, so you can do this to use token outside of the block:
val token = client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
response.body!!.string()
}
I am using Retrofit. Using Kotlin. I need to know the resonse status code. Like is it 200 or 500. How can I get it from the response ?
My Api class:
interface Api {
#POST("user/code/check")
fun checkSmsCode(#Body body: CheckCodeBody): Single<Response<Void>> }
This is how I am calling Api. But note that SERVE DOES NOT RETURN CODE FIELD IN RESPONSE BODY!
api.checkSmsCode(
CheckCodeBody(
code = code
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
//HOW TO CHECK STATUS RESPONSE STATUS CODE HERE???
},
{ e ->
when (e) {
is IOException -> view?.showNoNetworkAlert()
else -> view?.invalidCodeError()
}
}
).also {}
As I understood, in Java it was a easy peasy thing.
You just use response.code() or something similar and that's it. But how to achieve it in Kotlin?
so your on response should look something like this
override fun onResponse(call: Call<MyModel>?, response: Response<MyModel>?) {
//
}
})
then inside that you should just to able to do
override fun onResponse(call: Call<MyModel>?, response: Response<MyModel>?) {
response.code()
}
})
is this what your talking about?
yo need to use it
interface OnlineStoreService{
#Headers("Content-Type: application/json","Connection: close")
#POST
fun getDevices(
#Url url: String,
#Header("Authorization") token: String,
#Body apiParams: APIParams
): Observable<OnlineStoresInfo>
}
.subscribe({ onlineStoresInfo -> // or it -> where "it" it's your object response, in this case is my class OnlineStoresInfo
loading.value = false
devices.value = onlineStoresInfo.devices
}, { throwable ->
Log.e(this.javaClass.simpleName, "Error getDevices ", throwable)
loading.value = false
error.value = context.getString(R.string.error_information_default_html)
})
.subscribe({ it ->
// code
}, { throwable ->
//code
})
If you haven't configure your retrofit request method to return a Response<*> you won't be able to have the response code. Example:
interface SomeApi{
#POST("user/code/check")
fun checkSmsCode(#Body body: CheckCodeBody): Single<Response<String>>
}
And after you finish your request:
.subscribe({
//access response code here like : it.code()
//and you can access the response.body() for your data
//also you can ask if that response.isSuccessful
})
I have an OkHttp Interceptor which should ask for a token when the request is getting a 401 HTTP error. Now, the request to login service is done but then the chain is broken and the original request is not retried.
Here is the intercept method of my interceptor:
override fun intercept(chain: Interceptor.Chain): Response {
logger.d("AuthenticationServiceHolder $authenticationServiceHolder")
val originalRequest = chain.request()
logger.d("Intercepting call to ${originalRequest.method()} ${originalRequest.url()}")
val response: Response = chain.proceed(originalRequest)
val successful = response.isSuccessful
val code = response.code()
logger.d("Response successful: $successful - code: $code")
if (!successful && code == HttpURLConnection.HTTP_UNAUTHORIZED) {
logger.d("Token is $token")
val deviceUuid = deviceIdentificationManager.deviceUuid().blockingGet()
logger.d("Device uuid $deviceUuid")
if (deviceUuid != null) {
val authenticationService = authenticationServiceHolder.get()
if (authenticationService != null) {
token = reLogin(authenticationService, deviceUuid)
if (token != null) {
val headersBuilder = originalRequest.headers().newBuilder()
headersBuilder.removeAll(AUTHORIZATION_HEADER)
headersBuilder.add(AUTHORIZATION_HEADER, token!!)
val requestBuilder = originalRequest.newBuilder()
val request = requestBuilder.headers(headersBuilder.build()).build()
return chain.proceed(request)
} else {
logger.e("Token was not retrieved")
}
} else {
logger.e("Authentication service is null!")
}
}
}
return response
}
The reLogin() method is:
private fun reLogin(authenticationService: AuthenticationService, deviceUuid: UUID): String? {
logger.d("reLogin() - authenticationService $authenticationService")
val blockingGet = authenticationService?.login(LoginRequest(deviceUuid, clock.currentTime()))?.blockingGet()
logger.d("reLogin() - response $blockingGet")
val response = blockingGet ?: return null
logger.d("reLogin() - token ${response.token}")
return response.token
}
NEW:
As Mitesh Machhoya says, I've tried with 2 different instances of retrofit, one has the okhttp client with the interceptor and the another doesn't have it.
And now the login call is not intercepted but the execution of the Interceptor is broken, I mean the log trace of this class is:
- AuthenticationServiceHolder XXXmypackageXXX.AuthenticationServiceHolder...
- Intercepting call to GET XXXmyInterceptedCallXXX
- Response successful: false - code: 401
- Token is null
- Device uuid XXX
- reLogin() - authenticationService retrofit2.Retrofit$1#a5c0a25
And nothing more. I mean reLogin() - response..... is not printed. I'm sure that the login call is working because I see the login response in okhttp log.
Make reLogin request with other httpClient without attaching interceptor, then it will work well as you expected.
if you make reLogin request with same httpClient then it will gone through the interceptor and it override request everytime so try to make request using another httpClient
The code pasted is working well, the Login request is working but the response of login was changed on server side and the deserialisation made it crash and it broke the chain.
I am using OkHttp in my android application with several async requests. All requests require a token to be sent with the header. Sometimes I need to refresh the token using a RefreshToken, so I decided to use OkHttp's Authenticator class.
What will happen when 2 or more async requests get a 401 response code from the server at the same time? Would the Authenticator's authenticate() method be called for each request, or it will only called once for the first request that got a 401?
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException
{
return null;
}
How to refresh token only once?
Use a singleton Authenticator
Make sure the method you use to manipulate the token is Synchronized
Count the number of retries to prevent excessive numbers of refresh
token calls
Make sure the API calls to get a fresh token and the
local storage transactions to save the new token in your local stores are not asynchronous. Or if you want to make them asynchronous make sure you to you token related stuff after they are completed.
Check if the access token is refreshed by another thread already to
avoid requesting a new access token from back-end
Here is a sample in Kotlin
#SingleTon
class TokenAuthenticator #Inject constructor(
private val tokenRepository: TokenRepository
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return if (isRequestRequiresAuth(response)) {
val request = response.request()
authenticateRequestUsingFreshAccessToken(request, retryCount(request) + 1)
} else {
null
}
}
private fun retryCount(request: Request): Int =
request.header("RetryCount")?.toInt() ?: 0
#Synchronized
private fun authenticateRequestUsingFreshAccessToken(
request: Request,
retryCount: Int
): Request? {
if (retryCount > 2) return null
tokenRepository.getAccessToken()?.let { lastSavedAccessToken ->
val accessTokenOfRequest = request.header("Authorization") // Some string manipulation needed here to get the token if you have a Bearer token
if (accessTokenOfRequest != lastSavedAccessToken) {
return getNewRequest(request, retryCount, lastSavedAccessToken)
}
}
tokenRepository.getFreshAccessToken()?.let { freshAccessToken ->
return getNewRequest(request, retryCount, freshAccessToken)
}
return null
}
private fun getNewRequest(request: Request, retryCount: Int, accessToken: String): Request {
return request.newBuilder()
.header("Authorization", "Bearer " + accessToken)
.header("RetryCount", "$retryCount")
.build()
}
private fun isRequestRequiresAuth(response: Response): Boolean {
val header = response.request().header("Authorization")
return header != null && header.startsWith("Bearer ")
}
}
I see here two scenarios based on how API which you call works.
First one is definitely easier to handle - calling new credentials (e.g. access token) doesn't expire old one. To achieve it you can add an extra flag to your credentials to say that credentials are being refreshed. When you got 401 response, you set flag to true, make a request to get new credentials and you save them only if flag equals true so only first response will be handled and rest of them will be ignored. Make sure that your access to flag is synchronized.
Another scenario is a little bit more tricky - every time when you call new credentials old one are set to be expired by server side. To handle it you I would introduce new object to be used as a semafore - it would be blocked every time when 'credentials are being refreshed'. To make sure that you'll make only one 'refresh credentials' call, you need to call it in block of code which is synchronized with flag. It can look like it:
synchronized(stateObject) {
if(!stateObject.isBeingRefreshed) return;
Response response = client.execute(request);
apiClient.setCredentials(response.getNewCredentials());
stateObject.isBeingRefreshed = false;
}
As you've noticed there is an extra check if(!stateObject.isBeingRefreshed) return; to cancel requesting new credentials by following requests which received 401 response.
In my case I implemented the Authenticator using the Singleton pattern. You can made synchronized that method authenticate. In his implementation, I check if the token from the request (getting the Request object from Response object received in the params of authenticate method) is the same that the saved in the device (I save the token in a SharedPreferences object).
If the token is the same, that means that it has not been refresed yet, so I execute the token refresh and the current request again.
If the token is not the same, that means that it has been refreshed before, so I execute the request again but using the token saved in the device.
If you need more help, please tell me and I will put some code here.
This is my solution to make sure to refresh token only once in a multi-threading case, using okhttp3.Authenticator:
class Reauthenticator : Authenticator {
override fun authenticate(route: Route?, response: Response?): Request? {
if (response == null) return null
val originalRequest = response.request()
if (originalRequest.header("Authorization") != null) return null // Already failed to authenticate
if (!isTokenValid()) { // Check if token is saved locally
synchronized(this) {
if (!isTokenValid()) { // Double check if another thread already saved a token locally
val jwt = retrieveToken() // HTTP call to get token
saveToken(jwt)
}
}
}
return originalRequest.newBuilder()
.header("Authorization", getToken())
.build()
}
}
You can even write a unit test for this case, too! 🎉
Add synchronized to authenticate() method signature.
And make sure getToken() method is blocking.
#Nullable
#Override
public synchronized Request authenticate(Route route, Response response) {
String newAccessToken = getToken();
return response.request().newBuilder()
.header("Authorization", "Bearer " + newAccessToken)
.build();
}
Make sure to use singleton custom Authenticator
When refreshing token successful return request with new token else return null.
class TokenAuthenticator(
private val sharedPref: SharedPref,
private val tokenRefreshApi: TokenRefreshApi
) : Authenticator,
SafeApiCall {
override fun authenticate(route: Route?, response: Response): Request? {
return runBlocking {
when (val tokenResponse = getUpdatedToken()) {
is Resource.Success -> {
val token = tokenResponse.data.token
sharedPref.saveToken(token)
response.request.newBuilder().header("Authorization", "Bearer $token").build()
}
else -> {
null
}
}
}
}
private suspend fun getUpdatedToken(): Resource<LoginResponse> {
return safeApiCall { tokenRefreshApi.refreshToken("Bearer ${sharedPref.getToken()}") }
}
}