I'm trying to add AUTH Token to Retrofit
I'm saving token on preferences Datastore
but when I trying to retrieve it show null
enter image description here
My code like this and its working.
private fun getHeaderInterceptor():Interceptor{
return Interceptor { chain ->
val request =
chain.request().newBuilder()
.header("Cookie", cookie!!)
.build()
chain.proceed(request)
}
}
Full code: https://gist.github.com/Cr3bain/adf61caaa9da3291e1739c9bf0f334d8
Related
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 can I handle 'refresh token' when 'access_token' is expired?
I know how it works. But what I want to know is implementing once and apply it to all the APIs.
When access token is expired, all the APIs are blocked(401) and need to request new token with refresh token.
So, I tried to do it within 'intercepter' because it can handle the request and response before sending or before handling in the application.
The process is like this.
request an API
catch the response
if it's 401, call refresh token API
get the response and request the original API that I was going to call.
get the proper response from the original API.
// intercepter
val originalRequest = it.request()
val newRequestBuilder = originalRequest.newBuilder()
val response = it.proceed(newRequestBuilder.build())
if (response.code == 401) {
// TODO: refresh token and request again and get the original response
}
response
Refresh tokens without getting "Error" response from API (Write only once)
I would suggest you to use Authenticator. OkHttp will automatically ask the Authenticator for credentials when a response is 401 Not Authorized retrying last failed request with them.
Create a class MyAuthenticator and add the following code:
class MyAuthenticator: Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
// set maixmum retry count
if (response.responseCount >= 3) {
return null // If we've failed 3 times, give up.
}
// write code to refresh the token
val call = MyRetrofitClient.MyApi().refreshAccessToken()
val res = call.execute()
if (res.isSuccessful){
val newAccessToken = res.body // your new token from response
//
response.request
.newBuilder()
.header("bearerToken", newAccessToken)
.build()
}else{
return null
}
return null
}
//
private val Response.responseCount: Int
get() = generateSequence(this) { it.priorResponse }.count()
}
Now you can attach this Authenticator to your OkHttpClient the same way you do with Interceptors
private val client= OkHttpClient.Builder()
.addInterceptor(MyInterceptor())
.authenticator(MyAuthenticator()) // authenticator we created
.build()
Finally add this client to the Retrofit Builder:
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client) // from 2nd step
.build()
That's all, Now if 401 error occur, Authenticator will be called automatically and token will be refreshed and the pending API will be continued without getting error response.
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()
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"))
I'm trying to get posts from facebook page using Retrofit but I can't pass Access token and every time I get an error io.reactivex.exceptions.OnErrorNotImplementedException: HTTP 400 Bad Request
This is my code:
RetroAPI:
#GET("{page_id}/feed")
fun getPosts(#Path("page_id") pageId : String,
#Header("access_token") authToken : AccessToken)
: Observable<Posts>
Set Access Token:
AccessToken.setCurrentAccessToken(AccessToken("AppToken", "AppID","userID",null,null,null,null,null))
Get data:
var pagePosts : Observable<Posts> = facebook.getPosts("pageID", AccessToken.getCurrentAccessToken())
pagePosts.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({result ->
var a : Posts = result
var b : List<Data> = result.data
Log.d("Posts A","${a.data[1].id}")
Log.d("Data B", "$b")
})
Set RetrofitAPI:
private val facebook : RetroAPI
init{
val retrofit = Retrofit.Builder()
.baseUrl("https://graph.facebook.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
facebook = retrofit.create(RetroAPI::class.java)
}
Normally access_token is sent as a query param to authenticate your request.
However you can also send a header in the form
Authorization: Bearer XXX
But AFAIK sending access_token as a header is unsupported.