im trying to implement what said in this article:
https://blog.coinbase.com/okhttp-oauth-token-refreshes-b598f55dd3b2
im working on an android app using kotlin
for a coibase wallet.
I was able to get the authorization code, and then an authorization token with retrofit. i have used the token to get user information and also refresh a token. But when it came to create address i am getting wrong token response even if im using a newly created token by using the refresh token with the correct scope create wallet address
so as a suggestion , mentor on my course asked me to use that article implementation, so that tokens are refreshed automatically with correct headers and all.
so i can't find a way to implement correctly and cant find an example that uses code from the article.
ill share my code tomorrow, hope someone can help with this. Thank you for your time, i appreciate.
This is the code im trying to implement based on the article:
object UserNetwork {
//private val logger = HttpLoggingInterceptor()
// .setLevel(HttpLoggingInterceptor.Level.BODY )
private val accessTokenProvider = AccessTokenProviderImp()
private val accessTokenInterceptor = AccessTokenInterceptor(accessTokenProvider)
val client = OkHttpClient.Builder()
.addNetworkInterceptor(accessTokenInterceptor)
.authenticator(AccessTokenAuthenticator(accessTokenProvider))
.build()
val coinBaseClienApiCalls:CoinBaseClienApiCalls
get(){
return Retrofit.Builder()
.baseUrl("https://api.coinbase.com/")
.client(client)
.addConverterFactory(MoshiConverterFactory.create())
.build()
.create(CoinBaseClienApiCalls::class.java)
}
private class UserCallBack(
private val onSuccess:(UserData.Data) -> Unit): Callback<UserData> {
override fun onResponse(call: Call<UserData>, response: Response<UserData>) {
Log.e("ON Response User:"," ${response.body()?.data?.name}")
val newClient = UserData.Data(
name = response.body()?.data?.name?:"",
avatarUrl = response.body()?.data?.avatarUrl?:"",
id = response.body()?.data?.id?:"",
profileBio = response.body()?.data?.profileBio?:"",
profileLocation = response.body()?.data?.profileLocation?:"",
profileUrl = response.body()?.data?.profileUrl ?:"",
resource = response.body()?.data?.resource?:"",
resourcePath = response.body()?.data?.resourcePath?:"",
username = response.body()?.data?.username?:""
)
Log.e("RESPONDED WITH:","Client: ${newClient.name},${newClient.id} ${response.isSuccessful}")
onSuccess(newClient)
}
override fun onFailure(call: Call<UserData>, t: Throwable) {
Log.e("On Failure Address:","$t")
}
}
fun getUser (onSuccess: (UserData.Data) -> Unit){
var token = if(Repository.accessToken != ""){
Repository.accessToken
}else{
""
}
if(token != ""){
coinBaseClienApiCalls.getUser("Bearer $token").enqueue(UserCallBack(onSuccess)) //getUser(token).enqueue(AddressCallBack(onSuccess))
}else{
Log.e("ACCESS TOKEN IN REPOSITORY","${Repository.accessToken}")
}
}
}
class AccessTokenInterceptor(
private val tokenProvider: AccessTokenProvider
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = tokenProvider.token()
return if (token == null) {
chain.proceed(chain.request())
} else {
val authenticatedRequest = chain.request()
.newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
chain.proceed(authenticatedRequest)
}
}
}
interface AccessTokenProvider {
/**
* Returns an access token. In the event that you don't have a token return null.
*/
fun token(): String?
/**
* Refreshes the token and returns it. This call should be made synchronously.
* In the event that the token could not be refreshed return null.
*/
fun refreshToken(): String?
}
class AccessTokenAuthenticator(
private val tokenProvider: AccessTokenProvider
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
// We need to have a token in order to refresh it.
val token = tokenProvider.token() ?: return null
synchronized(this) {
val newToken = tokenProvider.token()
Log.e("NEW TOKEN AUTHENTICATOR","$newToken")
// Check if the request made was previously made as an authenticated request.
if (response.request.header("Authorization") != null) {
// If the token has changed since the request was made, use the new token.
if (newToken != token) {
Log.e("Testing Authenticator1","Testing1")
return response.request
.newBuilder()
.removeHeader("Authorization")
.addHeader("Authorization", "Bearer $newToken")
.build()
}
val updatedToken = tokenProvider.refreshToken() ?: return null
Log.e("Testing Authenticator2","Testing2")
// Retry the request with the new token.
return response.request
.newBuilder()
.removeHeader("Authorization")
.addHeader("Authorization", "Bearer $updatedToken")
.build()
}
}
return null
}
}
class AccessTokenProviderImp():AccessTokenProvider {
var token = Repository.accessToken
override fun token(): String? {
return token
}
override fun refreshToken(): String? {
return token
}
}
I fixed the way the AccessTokenProviderImp() requested the existing token or the newly refreshed token, remove the check if (newToken != token) from Authenticator. Works great.
Related
I've been stuck with this problem for some time now, and I can't seem to find the problem especially when all I did was following a guide online.
I'm trying to make a POST request, and receive a response in exchange:
Request body:
{
"Email":"test#gmail.com",
"firebaseUid":"Test_UID",
"IsBanned":1
}
Response body:
`
{
"Email": "test#gmail.com",
"UserId": 7
}
So basically whenever I submit a request to /users/ to create an account, I get both the email and UserId returned.
data class UserLogin(
#SerializedName("Email") val Email: String,
#SerializedName("UserId") val UserId: Int?,
#SerializedName("IsBanned") val IsBanned: Boolean?,
#SerializedName("firebaseUid") val firebaseUid: String?
)
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2/8000/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun<T> buildService(service: Class<T>): T{
return retrofit.create(service)
}
}
class RestApiService {
fun addUser(userData: UserLogin, onResult: (UserLogin?) -> Unit){
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<UserLogin> {
override fun onFailure(call: Call<UserLogin>, t: Throwable) {
Log.d("Failed retrofit",t.message.toString())
onResult(null)
}
override fun onResponse( call: Call<UserLogin>, response: Response<UserLogin>) {
val addedUser = response.body()
onResult(addedUser)
}
}
)
}
}
onFailure doesn't seem to be printing anything on the console. I'm calling the API from a button like this and both Email and UserId keep returning null for some reason:
`
val apiService = RestApiService()
val userInfo = UserLogin(UserId = null,firebaseUid = "TestTestTest", IsBanned = false, Email = "test#gmail.com");
apiService.addUser(userInfo){
Log.d("Retrofit user added", it?.Email.toString())
}
`
I tried to:
Set default values for the data class members.
Tried to check if response is successfull, and then print the errorBody if not. That didn't help either. I'm getting unreadable errors like $1#9afe35d instead.
Everything seem to be working fine when I do requests manually with POSTMAN.
It turned out there was nothing with the code. I just typed the port the wrong way and used a / instead of :
private val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2:8000/") // this
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
this is a self answered question about adding access token to request header and refresh the token with refresh token, I was struggling with this topic for a long time, and now I'm writing in post hopefully it could help anyone else in same situations
maybe there would be any other better solutions, but it worked for me in the easiest way
in remote module I'm following this method with the help of Hilt:
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
#Provides
#Singleton
fun providesRetrofit (okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
#Provides
#Singleton
fun providesOkHttpClient(interceptor: AuthInterceptor, authAuthenticator: AuthAuthenticator): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.authenticator(authAuthenticator)
.build()
}
I send a request to server and receive the access token and refresh token, then I saved them with the power of shared preferences like so:
class TokenManager #Inject constructor(#ApplicationContext context: Context) {
private var prefs: SharedPreferences =
context.getSharedPreferences(PREFS_TOKEN_FILE, Context.MODE_PRIVATE)
fun saveToken(token: UserAuthModel?) {
val editor = prefs.edit()
token?.let {
editor.putString(USER_TOKEN, token.access_token).apply()
editor.putString(USER_REFRESH_TOKEN,token.refresh_token).apply()
editor.putBoolean(IS_LOGGED_IN,true).apply ()
}
}
fun getToken(): String? {
return prefs.getString(USER_TOKEN, null)
}
fun getRefreshToken(): String? {
return prefs.getString(USER_REFRESH_TOKEN, null)
}}
then I use .addInterceptor(interceptor) in order to add header to all requests like this:
class AuthInterceptor #Inject constructor():Interceptor{
#Inject
lateinit var tokenManager: TokenManager
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
val token = tokenManager.getToken()
request.addHeader("Authorization", "Bearer $token")
request.addHeader("Accept","application/json")
return chain.proceed(request.build())
}}
after that you will have access to every method which requires access token as authentication modo, depending on your API instruction your access token will be expired in a specific time ( maybe 24 hours) and you need a new access token which is accessible with help of refresh token that you already have it, and then I add this line to okHttp .authenticator(authAuthenticator)
when your access token expires, the API will send you back a 401 or 403 error code (it will happen in interceptor section), and in that time Authenticator came into play, luckily it's smart enough to recognize this and do the task,
I take care of Authenticator like this:
class AuthAuthenticator #Inject constructor() : Authenticator {
#Inject lateinit var tokenManager: TokenManager
override fun authenticate(route: Route?, response: Response): Request? {
return runBlocking {
val refreshToken=tokenManager.getRefreshToken()
val refreshTokenR:RequestBody= refreshToken?.toRequestBody() ?: "".toRequestBody()
val grantTypeR:RequestBody= "refresh_token".toRequestBody()
//val newAccessToken = authService.safeRefreshTokenFromApi(refreshToken,grantType)
val newAccessToken = getUpdatedToken(refreshTokenR,grantTypeR)
if (!newAccessToken.isSuccessful){
val intent=Intent(context,MainActivity::class.java)
context.startActivity(intent)
}
tokenManager.saveToken(newAccessToken.body()) // save new access_token for next called
newAccessToken.body()?.let {
response.request.newBuilder()
.header("Authorization", "Bearer ${it.access_token}") // just only need to
override "Authorization" header, don't need to override all header since this new request
is create base on old request
.build()
}
} }
private suspend fun getUpdatedToken( refreshToken:RequestBody,grantType:RequestBody):
retrofit2.Response<UserAuthModel> {
val okHttpClient = OkHttpClient.Builder()
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
val service=retrofit.create(AuthService::class.java)
return service.refreshTokenFromApi(refreshToken,grantType)
}}
Authenticator need to make a request, so it needs a retrofit and OkHttp instance( which will run this very Authenticator),in order to break this cycle I created another instance .
two things I have to mention is :
I guess it's ok to use runBlocking because Authenticator itself is running on another thread
and remember in case of kotlin you have to use suspend function in API service to take care of Unable to create call adapter for retrofit2.Response error
at the end, I have to mention that I'm using two different API service like this:
general service:
interface MovieService {
#GET("api/v1/movies/{movie-id}")
suspend fun getSingleMovie(#Path("movie-id") movieId:Int):Response<NetworkMovieModel>}
authentication service:
interface AuthService:MovieService {
#Multipart
#POST("oauth/token")
fun refreshTokenFromApi (#Part("refresh_token") username: RequestBody,
#Part("grant_type") grantType: RequestBody
): Response<UserAuthModel>}
I try to send the refresh token to the server when the access token expires and receive a new accesss token, but my code does not work properly.
refresh token class
class RefreshToken(): Authenticator {
override fun authenticate(route: Route?, responsee: Response): Request? {
if (responsee.code == 401) {
lateinit var loginRepository: LoginRepository
lateinit var bodyRefresh: BodyRefresh
lateinit var access: StoreAccess //datastore for save token
lateinit var newAccess: String
CoroutineScope(Dispatchers.Main).launch {
access.getUserRefresh().collect {
val refresh = it.toString()
bodyRefresh.refresh = refresh
val response = loginRepository.RefreshAccess(bodyRefresh)
if (response.isSuccessful) {
access.saveUserRefresh(response.body()?.access.toString())
newAccess = response.body()?.access.toString()
}
}
}
return responsee.request.newBuilder().header("Authorization", "Bearer $newAccess.toString()")
.build()
} else {
return responsee.request
}
}
}
api service
#POST("token/refresh/")
suspend fun refreshAcssec(#Body refresh: BodyRefresh): Response<ResponseAcces>
I'm using runBlocking{ } instead of CoroutineScope(Dispatchers.Main).launch.
It's working for me, but I don't know if is the best solution
I guess it's ok, because Authenticator and Interceptor will run on another thread
check this
enter link description here
I need to make a sync call to reauthenticate the user and get a new token, but I haven't found a way that works. The code below blocks the thread and it is never unblocked, ie. I have an infinite loop
class ApolloAuthenticator(private val authenticated: Boolean) : Authenticator {
#Throws(IOException::class)
override fun authenticate(route: Route, response: Response): Request? {
// Refresh your access_token using a synchronous api request
if (response.request().header(HEADER_KEY_APOLLO_AUTHORIZATION) != null) {
return null //if you've tried to authorize and failed, give up
}
synchronized(this) {
refreshTokenSync() // This is blocked and never unblocked
val newToken = getApolloTokenFromSharedPreference()
return response.request().newBuilder()
.header(HEADER_KEY_APOLLO_AUTHORIZATION, newToken)
.build()
}
private fun refreshTokenSync(): EmptyResult {
//Refresh token, synchronously
val repository = Injection.provideSignInRepository()
return repository
.signInGraphQL()
.toBlocking()
.first()
}
fun signInGraphQL() : Observable<EmptyResult> =
sharedPreferencesDataSource.identifier
.flatMap { result -> graphqlAuthenticationDataSource.getAuth(result) }
.flatMap { result -> sharedPreferencesDataSource.saveApolloToken(result) }
.onErrorReturn { EmptyResult() }
}
---------- Use of it
val apollAuthenticator = ApolloAuthenticator(authenticated)
val okHttpBuilder =
OkHttpClient.Builder()
.authenticator(apollAuthenticator)
I haven't found a way to make a sync call using RxJava, but I can make it by using kotlin coutorine runBlocking, which will block the thread until the request is finished:
synchronized(this) {
runBlocking {
val subscription = ApolloReauthenticator.signInGraphQl() // await until it's finished
subscription.unsubscribe()
}
}
fun signInGraphQl(): Subscription {
return repository.refreshToken()
.subscribe(
{ Observable.just(EmptyResult()) },
{ Observable.just(EmptyResult()) }
)
}
I have a completable method for authentication and I want to retrieve the authenticated user details on login success.
After retrieving them, I want to call an onUserAuthenticated method.
I am doing this with a nested completable (2 levels deep), as I want to sent both the authorization token received on login and the user details to the onUserAuthenticated method.
The problem is that onUserAuthenticated never gets invoked.
class LoginViewModel(val emailAuthentication: EmailAuthentication) : ViewModel() {
val email = MutableLiveData<String>()
val password = MutableLiveData<String>()
fun login() {
emailAuthentication.login(email = email.value!!, password = password.value!!)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Timber.d("User $email logged in")
}, { error ->
Timber.e(error, "Error logging in $email")
})
}
}
class EmailAuthenticationImpl(private val authentication: Authentication,
private val userRepository: UserRepository,
private val authRepository: AuthenticationRepository
) : EmailAuthentication {
override fun register(email: String, password: String): Completable {
return userRepository.register(email, password)
}
override fun login(email: String, password: String): Completable {
// some missing fields validation
return authRepository.login(email, password)
.flatMapCompletable { token ->
userRepository.getCurrentUser()
.flatMapCompletable {
Completable.defer {
// FIXME this never gets invoked
authentication.onUserAuthenticated(AuthType.EMAIL, it, token)
Completable.complete()
}
}
}
}
I tried putting Completable.defer also only before userRepository.getCurrentUser() and both before userRepository.getCurrentUser() and before authentication.onUserAuthenticated(AuthType.EMAIL, it, token), but the code is never reached.
What am I doing wrong?