How to regenerate token in Android GraphQL? - android

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()

Related

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.

How to get Context inside an Object?

I am using retrofit to fetch some data and for that I am passing a token in Header for Authentication.
I want to fetch the token from the Shared Preferences in my Retrofit Client Object but I don't know how to?
I tried to get a context in the object using a function but then it gives me WARNING that
Do not place Android context classes in static fields (static reference to RetrofitClient which has field context pointing to Context); this is a memory leak (and also breaks Instant Run) less...
Also i tried to get context in my interface of retrofit and I got the context without warning but I don't know where to get Shared Preferences.
interface Api {
var context:Context;
#FormUrlEncoded
#POST("getMerchantProductsSlideContent")
fun getProductsForSlide(
//Don't know how to get value from shared refercne to this header
#Header("Authentication: Bearer ")
#Field("token") token:String,
#Field("deviceId") deviceId:String,
#Field("content_receiver") content_receiver:String,
#Field("content_type") content_type:String,
#Field("data") data:Array<String>
):Call<DefaultResponse>
fun getContext(mContext:Context){
context = mContext
}
}
This is retrofitClient.kt
object RetrofitClient {
private val AUTH = "Bearer $token"
private const val BASE_URL = "http://192.168.1.5/Projects/Sitapuriya/public/"
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
.addHeader("Authorization", AUTH)
.method(original.method(), original.body())
val request = requestBuilder.build()
chain.proceed(request)
}.build()
val instance: Api by lazy{
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
retrofit.create(Api::class.java)
}
}
This is my retrofit interface
interface Api {
#FormUrlEncoded
#POST("getMerchantProductsSlideContent")
fun getProductsForSlide(
#Field("token2") token2:String,
#Field("deviceId") deviceId:String,
#Field("content_receiver") content_receiver:String,
#Field("content_type") content_type:String,
#Field("data") data:Array<String>
):Call<DefaultResponse>
}
[UPDATED] This is my activity n which i am calling the retrofit
val data = arrayOf(merchantId)
RetrofitClient.instance.getContext(this)
RetrofitClient.instance.getProductsForSlide(
token,
deviceId,
"MERCHANT",
"MERCHANT_VIEW_BASIC",
data
).enqueue(object:Callback<DefaultResponse>{
override fun onFailure(call: Call<DefaultResponse>, t: Throwable) {
Toast.makeText(applicationContext,"ERROR: ${t.message}",Toast.LENGTH_LONG).show()
}
override fun onResponse(
call: Call<DefaultResponse>,
response: retrofit2.Response<DefaultResponse>
) {
Toast.makeText(applicationContext,"SUCCESS: ${response.body()?.content}",Toast.LENGTH_LONG).show()
}
})
I want to get the token from Shared Preferences and use it as a header for my request and I know to access Shared Preferences we need a context. How can I get the context in Object?
[UPDATE-2] Tried #Blundell answer
interface Api {
var token: String
#FormUrlEncoded
#POST("getMerchantProductsSlideContent")
fun getProductsForSlide(
#Header("Authentication: Bearer $token")
#Field("token") token:String,
#Field("deviceId") deviceId:String,
#Field("content_receiver") content_receiver:String,
#Field("content_type") content_type:String,
#Field("data") data:Array<String>
):Call<DefaultResponse>
fun setAuthHeader(token2:String){
token = token2
}
}
But it gives error: An annotation argument must be a compile-time constant
Try to get token in your activity (you can use activity's context and get token from shared preferences) and pass this token to your retrofit class.
Also try to read something about dependency injection, dagger2, koin etc to provide different dependencies to your classes
interface Api {
#FormUrlEncoded
#POST("getMerchantProductsSlideContent")
fun getProductsForSlide(
#Header("Authentication") token:String,
#Field("deviceId") deviceId:String,
#Field("content_receiver") content_receiver:String,
#Field("content_type") content_type:String,
#Field("data") data:Array<String>
):Call<DefaultResponse>
}
In your activity:
val prefToken = // get it from prefences
val token = "Bearer " + prefToken
Instead of trying to store the context in a singleton, store the header you want to send. Access the context & sharedpreferences in your Activity.
Change:
RetrofitClient.instance.getContext(this)
To something like
RetrofitClient.instance.setAuthHeader(getSharedPreferences().getString("Header"))

How to stop Okhttp Authenticator from being called multiple times?

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.

Retrofit2: how to save cookies from response

I need to add some authorization information from cookie in response to next requests.
It works in postman - I make authorization request, then second request, which works fine. But if I delete cookies - second request returns error and I have to do authorization request again.
But in my application this second request always returns the same error. I tried to find needed cookie by using interceptor, but I hadn't found it
val client = OkHttpClient.Builder()
.addInterceptor(OAuthInterceptor())
private class OAuthInterceptor : Interceptor {
override fun intercept(chain: Chain): Response {
val request = chain.request()
com.app.logic.toLog("${chain.proceed(request).header("set-cookie")} ") // it's not that cookie what I looking for
val headers = chain.proceed(request).headers()
headers.names().forEach {
val s = headers.get(it)
com.app.logic.toLog("$it -> $s")
}
return chain + (Session.authConsumer?.let { consumer ->
consumer.sign(request).unwrap() as Request
} ?: request)
}
}
Does anybody know what else could I try?
So, finally I've found solution for working with cookies
val client = OkHttpClient.Builder()
.cookieJar(UvCookieJar())
private class UvCookieJar : CookieJar {
private val cookies = mutableListOf<Cookie>()
override fun saveFromResponse(url: HttpUrl, cookieList: List<Cookie>) {
cookies.clear()
cookies.addAll(cookieList)
}
override fun loadForRequest(url: HttpUrl): List<Cookie> =
cookies
}
You can use this this gist on how to intercept cookies when you receive them and send them back in your request in header.

Singleton that will hold an instance of Retrofit

So I have created a login that will take an username and password input from the user, encode it with Base64 in order to create a token in the format: ("Authorization", AUTH) where AUTH = "Basic " + Base64 encoding of user and password. This is sent via Headers.
So, in the end, it looks like this: Authorization: Basic XXXXXX, where XXXXXX is the user token.
And then it will check whether or not that user exists in the database via an API request.
I am using Retrofit and OkHttp3 in the same class as RetrofitClient and this class is responsible for using the API and adding those Headers.
Later, I use the RetrofitClient class on the Login Activity.
What I need to do now, is make this "token" available to all the other activities by creating a Singleton that will store the data of the Retrofit after a successful login. But I do not know how to do this.
I started learning Kotlin and Android 3 weeks ago.
Here is my code:
GET_LOGIN.kt
interface GET_LOGIN {
#GET("login")
fun getAccessToken() : Call<String>
}
RetrofitClient.kt
class RetrofitClient {
fun login(username:String, password:String){
val credentials = username + ":" + password
val AUTH = "Basic " + Base64.encodeToString(credentials.toByteArray(Charsets.UTF_8), Base64.DEFAULT).trim()
retrofit = init(AUTH)
}
// Initializing Retrofit
fun init(AUTH: String) : Retrofit{
// Creating the instance of an Interceptor
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
// Creating the OkHttp Builder
val client = OkHttpClient().newBuilder()
// Creating the custom Interceptor with Headers
val interceptor = Interceptor { chain ->
val request = chain?.request()?.newBuilder()?.addHeader("Authorization", AUTH)?.build()
chain?.proceed(request)
}
client.addInterceptor(interceptor) // Attaching the Interceptor
//client.addInterceptor(logging) // Attaching the Interceptor
// Creating the instance of a Builder
val retrofit = Retrofit.Builder()
.baseUrl("https://srodki.herokuapp.com/") // The API server
.client(client.build()) // Adding Http Client
.addConverterFactory(GsonConverterFactory.create()) // Object Converter
.build()
return retrofit
}
lateinit var retrofit : Retrofit
fun providesGetLogin(): GET_LOGIN = retrofit.create(GET_LOGIN::class.java)
}
LoginActivity.kt
var RetrofitClient : RetrofitClient = RetrofitClient()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
loginBtn.setOnClickListener {
val user = userTxt.text.toString()
val pass = passTxt.text.toString()
if (validateLogin(user, pass)){
login(user, pass)
}
}
}
fun validateLogin(user: String, pass: String): Boolean {
if (user == null || user.trim().isEmpty()){
Toast.makeText(this, "Missing Username or Password", Toast.LENGTH_SHORT).show()
return false
}
if (pass == null || pass.trim().isEmpty()){
Toast.makeText(this, "Missing Username or Password", Toast.LENGTH_SHORT).show()
return false
}
return true
}
fun login(user: String, pass: String) {
RetrofitClient.login(user, pass)
val apiLogin = RetrofitClient.providesGetLogin().getAccessToken()
apiLogin.enqueue(object : Callback<LoginResponse> {
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
if(response.isSuccessful){
if(response.body()?.code == 0){
Toast.makeText(this#LoginActivity, "Login Successful!", Toast.LENGTH_SHORT).show()
val intent = Intent(this#LoginActivity, List_usersActivity::class.java)
startActivity(intent)
} else {
Toast.makeText(this#LoginActivity, "Login Failed.", Toast.LENGTH_SHORT).show()
}
}
}
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
Toast.makeText(this#LoginActivity, "Login Failed.", Toast.LENGTH_SHORT).show()
}
})
}
}
first and foremost, please use camel case on java and kotlin. We have standards in java and kotlin on programming. And i can see that you are trying to do DI, but, thats not how you do it in Android.
Anyways, you could do this a couple of ways without even using a singleton but by saving it on a storage. Options are Shared Preferences, Local Storage and SQLite. But, if you insist on using a singleton. You can do it like this:
object MySingleton { // This is how you declare singletons in kotlin
lateinit var token: String;
}
EDIT
So, from your comment, it looked like you need to store the token. You could start by using sharedpreferences(database would be better) and store the token there. I assume you don't know how to so here is an example:
val sp = SharedPreferences("sp", 0);
sp.edit().putString("token", theTokenVariable); // not sure of this function
sp.edit().apply(); // you could use commit if you dont mind sharedpreferences to lag your screen(if it ever will)
Now how do you get the token from retrofit? The only way i could help you right now is that you could retrieve the response body from the response variable you receive from onResponse of the retrofit call. From there it is your problem mate. I don't know how your response is formatted, how it should be retrieved etc. A recommendation would be to format it as JSON.

Categories

Resources