How to stop Okhttp Authenticator from being called multiple times? - android

I have an Authenticator like this
#Singleton
class TokenAutheticator #Inject constructor(private val tokenHolder: Lazy<TokenHolder>,private val tokenInterceptor: TokenInterceptor):Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
val resp = tokenHolder.get().tokenService.relogin(tokenInterceptor.token).execute()
println("### res "+resp.code())
if (resp.code()==200) {
val body = tokenHolder.get().tokenService.relogin(tokenInterceptor.token).execute()?.body()
val newToken = body?.token
println("########## authenticator ########## ")
val url = route?.address()?.url()?.newBuilder()?.addQueryParameter("token", newToken)?.build()
return response.request().newBuilder().url(url).build()
}else{
return null
}
}
}
When the resp.code != 200 the Authenticator is called multiple times.
I am plugging it in Okhttp like this
#Provides
#Singleton
fun provideOkhttp(tokenInterceptor: TokenInterceptor, tokenAutheticator: TokenAutheticator): OkHttpClient {
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.authenticator(tokenAutheticator)
.addInterceptor(logging)
.addInterceptor(tokenInterceptor)
.build()
return client
}
So what I want to do is have the Authenticator try it only once and if it is able to get a new token then use the new token from now on and if it can't get a new token then exit. But the Authenticator is called multiple times and the API responds with Too many attempts. Any help is appreciated. Thanks

Only when you return null, Authenticator will stop getting called (stop retrying authentication).
Since you always return something, when response code is 200, it will get called a few times. Luckily, as far I understand, Authenticator detects your endless loop, and breaks out of it eventually.
Basically, you should use Authenticator to react to 401 (and similar) response code and only add authorization header in that case.

Related

Use Dagger to inject different Retrofit and OkHttp object before and after login

I have my Retrofit and OkHttp instances injected. I am changing the headers of my OkHttp object using an Interceptor. In this Interceptor, I set information such as app name, version etc.
I am now adding a login and want to set an authToken to the headers. However, this only exists AFTER login, obviously. So what I want to do is be able to add a new header after login for authToken. However, I can't get access to my OkHttp object as its buried in my module, and I just get my RestClient object injected in my class, with everything else built.
I was thinking of copying all my provides... code and making duplicates for before an after Login. For example, wrap my RestClient in a class called "PreLoginApi" that doesn't have authToken, and then creating another wrapper class called "PostLoginApi" which has the authToken, but this won't work if Dagger creates the objects on app launch.
#Provides
#Singleton
#Named("HeaderInterceptor")
fun provideHeaderInterceptor(context: Context): Interceptor {
val headerMap = context.getHeaderMap()
return Interceptor { chain ->
val original = chain.request()
val builder = original.newBuilder()
headerMap.keys.forEach {
builder.addHeader(it, headerMap[it] ?: "")
}
val request = builder.build()
chain.proceed(request)
}
}
#Provides
fun providesOkHttpClient(
#Named("HeaderInterceptor") headerInterceptor: Interceptor,
): OkHttpClient {
val builder = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.addInterceptor(headerInterceptor)
return builder.build()
}
You can see here where I call my extension function Context.getHeaderMap(). Inside this function, I'm retrieving the authToken from EncryptedSharedPreferences.
So ideally, I'd like to be able to tell Dagger to redo this initialisation once I login, because at this stage the authToken will be set. Is it possible to just re-initialise this one module or will I have to wrap my RestClient in another class, and maybe use Lazy loading to initialise the "PostLoginApi"?
Thanks.
EDIT:
I do similar for Firebase FCM token, where I pass in the headerMap during initialisation, like this:
private fun updateFirebaseToken(headerMap: HashMap<String, String>) {
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
return#OnCompleteListener
}
// Get new FCM registration token
task.result?.let { token ->
headerMap["FirebaseId"] = token
}
})
}
But I don't think I can do something like this for a SharedPrefs value.

How to regenerate token in Android GraphQL?

What is the better approach to regenerate the JWT token using refresh token in Apollo Graphql in Andriod Kotlin?.
Now we are using an Auth interceptor that extends ApolloInterceptor to attach the JWT token in the request header.
I want to check whether the Toke expires from here and it should call another mutation to generate a new token. Then it should proceed with the previous call with the new token.
Please refer to the code below
class AuthInterceptor(private val jwtToken: String) : ApolloInterceptor {
override fun interceptAsync(
request: ApolloInterceptor.InterceptorRequest,
chain: ApolloInterceptorChain,
dispatcher: Executor,
callBack: ApolloInterceptor.CallBack
) {
val header = request.requestHeaders.toBuilder().addHeader(
HEADER_AUTHORIZATION,
"$HEADER_AUTHORIZATION_BEARER $jwtToken"
).build()
chain.proceedAsync(request.toBuilder().requestHeaders(header).build(), dispatcher, callBack)
}
override fun dispose() {}
companion object {
private const val HEADER_AUTHORIZATION = "Authorization"
private const val HEADER_AUTHORIZATION_BEARER = "Bearer"
}
}
If you are using Apollo Android Client V3 and Using Kotlin Coroutine then use Apollo Runtime Dependency and Try HttpInterceptor instead of ApolloInterceptor. I think this is the better/best approach. For Reference Click Here
In your app-level build.gradle file
plugins {
......
id("com.apollographql.apollo3").version("3.5.0")
.....
}
dependencies {
implementation("com.apollographql.apollo3:apollo-runtime")
}
Now write your interceptor for the Apollo client.
FYI: If you've added the Authorization header using Interceptor or using addHttpHeader in client already then remove it or don't add header here val response = chain.proceed(request.newBuilder().addHeader("Authorization", "Bearer $token").build()), just build the request. Otherwise Authorization header will add multiple times in the request. So, be careful.
class AuthorizationInterceptor #Inject constructor(
val tokenRepo: YourTokenRepo
) : HttpInterceptor {
private val mutex = Mutex()
override suspend fun intercept(request: HttpRequest, chain: HttpInterceptorChain): HttpResponse {
var token = mutex.withLock {
// get current token
}
val response = chain.proceed(request.newBuilder().addHeader("Authorization", "Bearer $token").build())
return if (response.statusCode == 401) {
token = mutex.withLock {
// get new token from your refresh token API
}
chain.proceed(request.newBuilder().addHeader("Authorization", "Bearer $token").build())
} else {
response
}
}
}
Configure your Apollo client like below.
ApolloClient.Builder()
.httpServerUrl(BASE_GRAPHQL_URL)
.webSocketServerUrl(BASE_GRAPHQL_WEBSOCKET_ENDPOINT) // if needed
.addHttpHeader("Accept", "application/json")
.addHttpHeader("Content-Type", "application/json")
.addHttpHeader("User-Agent", userAgent)
.addHttpInterceptor(AuthorizationInterceptor(YourTokenRepo))
.httpExposeErrorBody(true)
.build()

OkHttpClient injected with Koin loses Authorization header

I'm adding DI to the existing project, in process I faced problem that header Authorization disappears from request. There is no any exceptions or logs from Retrofit/OkHttp. My dependencies are:
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.okhttp:okhttp:2.7.5'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'org.koin:koin-android:2.1.3'
I create http client using provideClient:
class OkHttpProvider private constructor() {
companion object {
fun provideClient(credentials: UsernamePasswordCredentials? = null, context: Context): OkHttpClient {
val client = OkHttpClient.Builder()
// logs
if (BuildConfig.DEBUG) {
client.addInterceptor(
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
)
}
if (credentials != null) {
val creds = Credentials.basic(credentials.userName, credentials.password)
val headerInterceptor = Interceptor { chain ->
var request = chain.request()
val headers = request
.headers()
.newBuilder()
.add("Authorization", creds)
.build()
request = request.newBuilder().headers(headers).build()
chain.proceed(request)
}
//client.addInterceptor(AccessTokenInterceptor(credentials))
client.addInterceptor(headerInterceptor)
}
client
.callTimeout(60L, TimeUnit.SECONDS)
.connectTimeout(10L, TimeUnit.SECONDS)
.readTimeout(60L, TimeUnit.SECONDS)
.writeTimeout(60L, TimeUnit.SECONDS)
.sslSocketFactory(getSslContext().socketFactory).hostnameVerifier { _, _ -> true }
client.addInterceptor(ChuckInterceptor(context))
return client.build()
}
private fun getSslContext(): SSLContext {
...implementation...
}
}
}
My modules for http client and Retrofit are below:
object HttpClientModule {
val module = module {
single(named(COMMON)) {
OkHttpProvider.provideClient(
get<SharedPreferenceManager>().getUserCredentials(),
androidContext()
)
}
...other versions...
}
const val COMMON = "common"
}
object ApiModule {
val module = module {
single {
RetrofitFactory.getServiceInstance(
ApiService::class.java,
get<SharedPreferenceManager>().getString(LocalDataSource.BUILD_OPTION_API, ""),
get(named(HttpClientModule.COMMON))
)
}
...other apis...
}
}
object RetrofitFactory {
const val GEO_URL = "http://maps.googleapis.com/maps/api/"
fun <T> getServiceInstance(
clazz: Class<T>,
url: String = GEO_URL,
client: OkHttpClient
): T = getRetrofitInstance(url, client).create(clazz)
private fun getRetrofitInstance(
url: String,
client: OkHttpClient
) = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
}
App starts to work with "admin" user and has some credentials saved in shared preferences, when user starts login with phone and sms and requests are sent with "admin" Authorization header, when user inputs code from sms and his new user credentials are saved in shared preferences. After that app sends two requests and Authorization header isn't presented in them. I saw it in Chuck, I even rechecked it using Charles.
To fix this problem I tried few solutions. Firstly, I changed inject for http client from single to factory, that didn't work. Secondly, I googled the problem, but I didn't mentions of this phenomenon. Thirdly, I wrote AccessTokenInterceptor according to this article and also cover everything with logs. I noticed that interceptor works fine in normal cases, but when Authorization header is missing method intercept is not called. This might be reason why default headerInterceptor also not working. Fourthly, I upgraded versions of Retrofit and OkHttp, this also didn't helped.
I noticed interesting thing about that bug: if I restart app after Retrofit lost Authorization header, app works fine test user is properly logged with correct token. Any attempts to relog without restarting the app fails. Maybe someone had similar problem or knows what is happening here, any ideas are welcomed.
I finally find solution to this problem. The problem was user credentials was passed to provideClient only once, when it's created. At that moment user was logged as admin, and standard user credentials was empty, so http client for ApiService was created without Authorization header.
To solve this I changed AccessTokenInterceptor form article (HttpClientType is a enum to select which credentials need to use):
class AccessTokenInterceptor(
private val sharedPreferenceManager: SharedPreferenceManager,
private val clientType: OkHttpProvider.HttpClientType
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val credentials = getUserCredentials(clientType)
if (credentials != null) {
val accessToken = Credentials.basic(credentials.userName, credentials.password)
val request = newRequestWithAccessToken(chain.request(), accessToken)
return chain.proceed(request)
} else {
return chain.proceed(chain.request())
}
}
private fun getUserCredentials(clientType: OkHttpProvider.HttpClientType): UsernamePasswordCredentials? {
return when (clientType) {
OkHttpProvider.HttpClientType.COMMON -> sharedPreferenceManager.getUserCredentials()
OkHttpProvider.HttpClientType.ADMIN -> ServiceCredentialsUtils.getCredentials(sharedPreferenceManager)
}
}
private fun newRequestWithAccessToken(#NonNull request: Request, #NonNull accessToken: String): Request {
return if (request.header("Authorization") == null) {
request.newBuilder()
.header("Authorization", accessToken)
.build()
} else {
request
}
}
}
Now each time request is sending, Interceptor gets user's credentials and adds header to request.

Kotlin Android oauth2 token request only returning errors

I am working on an user app for a local charitable organization, and need to access their API. The API is from wild apricot, and this is the documentation for making a token request:
Authentication tokens are obtained from Wild Apricot's authentication service, which is located at https://oauth.wildapricot.org. This service adheres to oAuth 2.0.
This is the access option I need to implement:
-----------------In order to obtain access token with API key, you have to make the following request:
POST /auth/token HTTP/1.1
Host: oauth.wildapricot.org
Authorization: Basic BASE64_ENCODED("APIKEY:YOUR_API_KEY")
Content-type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=auto
-------------------------------So. finally your request will look like:
POST /auth/token HTTP/1.1
Host: oauth.wildapricot.org
Authorization: Basic QVBJS0VZOm85c2U4N3Jnb2l5c29lcjk4MDcwOS0=
Content-type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=auto
I am attempting to make this call with retrofit2, and an okhttp3 interceptor, and getting a bad request response (I am very much new and learning, and have not been able to get anything other response than a 400 bad request (when I use "/auth/token" as the endpoint), or a 404 not found (when I use "/auth/token HTTP/1.1" as the endpoint). If someone could tell me where exactly I am messing this up It would be greatly appreciated, the code I have tried is below.
Interface:
interface WAApiCall {
#POST("auth/token")
fun callPost(#Body body:String ): Call<AuthToken>
}
Call Service:
object WAApiCallService {
private const val API_KEY = "xxxxxxxxIxHavexAxValidxKeyxxxx"
private const val BASE_URL = "https://oauth.wildapricot.org/"
private val AUTH = "Basic" + Base64.encodeToString(API_KEY.toByteArray(), Base64.NO_WRAP)
private const val CONTENT_TYPE = "application/x-www-form-urlencoded"
private var api:WAApiCall? = null
private fun getWAApi(context: Context) : WAApiCall {
if(api==null){
val OkHttpClient = OkHttpClient.Builder()
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BASIC
OkHttpClient.addInterceptor{chain ->
val request = chain.request()
Log.d("CALL", request.body.toString())
val newRequest = request.newBuilder()
.addHeader("Host", "oauth.wildapricot.org")
.addHeader("Authorization", AUTH )
.addHeader("Content-type", CONTENT_TYPE)
.method(request.method, request.body)
.build()
chain.proceed(newRequest)
}
api = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient.build())
.build()
.create(WAApiCall::class.java)
}
return api!!
}
fun call(context: Context) =
getWAApi(context)
}
Function in Main Activity to make the call:
fun testRequest(){
val call = WAApiCallService.call(this)
call.callPost("grant_type=client_credentials&scope=auto")
.enqueue(object: Callback<AuthToken>{
override fun onFailure(call: Call<AuthToken>, t: Throwable) {
Log.i("FAILURE", t.localizedMessage)
}
override fun onResponse(call: Call<AuthToken>, response: Response<AuthToken>) {
Log.i("SUCCESS", "TOKEN = ${response.body().toString()}")
Log.i("SUCCESS", "${response}")
val token = response.body()?.accessToken
Log.i("SUCCESS", "TOKEN = $token")
}
})
}
Error message:
I/SUCCESS: TOKEN = null
I/SUCCESS: Response{protocol=http/1.1, code=400, message=Bad Request, url=https://oauth.wildapricot.org/auth/token}
I think that I am just not understanding how to implement this type of request in some basic way, I could not get it to work in Postman either. I understand that I need to send the credentials to the authentication server, and receive an access token, that will expire and need to be refreshed, and that It will be included in each actual API endpoint call, I guess I'm just missing something crucial in the most important step of that process (getting the actual token, I am imagining it is a simple, forehead slapping kind of misunderstanding on my part?). The wild apricot API is on swagger hub, and I am able to gain access through that UI, with my API key, and see the responses, so I know that it is valid.
Your client credentials request looks mostly all good. The only thing I can see that looks wrong is no space character in the AUTH header between 'Basic' and the encoded credential.
If that doesn't work, could you trace the HTTP request and verify that you are sending the message you think you are.
Thank you for that observation, it led me to figuring out what ultimately was wrong in my initial attempt. After adding that space, I traced the request and found that It was actually sending two headers for content type.
The fix for that was to set the header in the retrofit call from the interface:
interface WAApiCall {
#POST("auth/token")
fun callPost(#Body Body: okhttp3.RequestBody, #Header("Content-type") type: String): Call<AuthToken>
}
As you can see the body is also slightly different, the call was getting through but was returning:
"unsupported_grant_type".
I was passing a raw string as the body parameter, which was including the quotation marks in the request. The solution there was to pass the okhttp3.Request body type rather than a raw string, in the function that makes the actual call it looks like this:
val body: "grant_type=client_credentials&scope=auto&obtain_refresh_token=true"
val requestBody = RequestBody.create("text/plain".toMediaTypeOrNull(),body)
val call = WAApiCallService.call(this)
call.callPost(requestBody,"application/x-www-form-urlencoded")
.enqueue(object: Callback<AuthToken>{
With those changes the call succeeds and my long running headache is over.

Kotlin : Okhttpclient correct way of creating single instance? OkHttpClient doesn't go through server ip check?

I'm new at android kotlin development and currently trying to solve how to correctly create a single instance of OkHttpClient for app-wide usage. I've currently sort-of* created a single instance of client and using it to communicate with the server, however currently the back-end server is not using token/userid for validation but IP check. I can log in the user no problem, but after going to another activity trying to call api, I'm being blocked access by server because apparently IP is not the same. I've used POSTMAN as well as already created a same functioning iOS app that is working with no issue. So my question is am i creating the single instance of OkHttpClient wrong? Or is OkHttpClient not suitable for this kind of ipcheck system? Should i use other library, and if yes, any suggestion and examples?
Thanks in advance
Currently i tried creating it like this :
class MyApplication: Application(){
companion object{
lateinit var client: OkHttpClient
}
override fun onCreate(){
super.onCreate()
client = OkHttpClient()
}
}
Then i created a helper class for it :
class OkHttpRequest {
private var client : OkHttpClient = MyApplication.client
fun POST(url: String, parameters: HashMap<String, String>, callback: Callback): Call {
val builder = FormBody.Builder()
val it = parameters.entries.iterator()
while (it.hasNext()) {
val pair = it.next() as Map.Entry<*, *>
builder.add(pair.key.toString(), pair.value.toString())
}
val formBody = builder.build()
val request = Request.Builder()
.url(url)
.post(formBody)
.build()
val call = client.newCall(request)
call.enqueue(callback)
return call
}
fun GET(url: String, callback: Callback): Call {
val request = Request.Builder()
.url(url)
.build()
val call = client.newCall(request)
call.enqueue(callback)
return call
}
}
Finally I'm using it like this :
val loginUrl = MyApplication.postLoginUrl
var userIdValue = user_id_textfield.text.toString()
var passwordValue = password_textfield.text.toString()
val map: HashMap<String, String> = hashMapOf("email" to userIdValue, "password" to passwordValue)
var request = OkHttpRequest()
request.POST(loginUrl, map, object : Callback {
val responseData = response.body?.string()
// do something with response Data
}
And on another activity after user log in :
val getPaidTo = MyApplication.getPaidTo
var request = OkHttpRequest()
request.GET(getPaidTo, object: Callback{
//do something with data
}
First, don't use your OkHttpClient directly in every Activity or Fragment, use DI and move all of your business logic into Repository or some source of data.
Here I will share some easy way to make REST request with Retrofit, OkHttpClient and Koin, if you want use the same:
WebServiceModule:
val webServiceModule = module {
//Create HttpLoggingInterceptor
single { createLoggingInterceptor() }
//Create OkHttpClient
single { createOkHttpClient(get()) }
//Create WebServiceApi
single { createWebServiceApi(get()) }
}
/**
* Setup a Retrofit.Builder and create a WebServiceApi instance which will hold all HTTP requests
*
* #okHttpClient Factory for HTTP calls
*/
private fun createWebServiceApi(okHttpClient: OkHttpClient): WebServiceApi {
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.REST_SERVICE_BASE_URL)
.client(okHttpClient)
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
return retrofit.create(WebServiceApi::class.java)
}
/**
* Create a OkHttpClient which is used to send HTTP requests and read their responses.
*
* #loggingInterceptor logging interceptor
*/
private fun createOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.readTimeout(defaultTimeout, TimeUnit.SECONDS)
.connectTimeout(defaultTimeout, TimeUnit.SECONDS)
.build()
}
And now you can inject your WebServiceApi everywhere, but better inject it in your Repository and then use it from some ViewModel
ViewModelModule:
val viewModelModule = module {
//Create an instance of MyRepository
single { MyRepository(webServiceApi = get()) }
}
Hope this help somehow
Okay, after i check with the back-end developer, i figured out the problem wasn't the ip address(it stays the same) but that the cookie was not saved by okhttp, both POSTMan and xcode automatically save the token returned into cookie so i never noticed that was the problem. So after googling a-bit, the solution can be as easy as this:
class MyApplication : Application(){
override fun onCreate(){
val cookieJar = PersistentCookieJar(SetCookieCache(),SharedPrefsCookiePersistor(this))
client = OkHttpClient.Builder()
.cookieJar(cookieJar)
.build()
}
}
With adding persistentCookieJar to gradle.

Categories

Resources