JWT Authentication and Refresh Token in Android with Retrofit Interceptor & Authenticator
Tags: android, kotlin, okhttp
Published: 2022-12-30 12:16:10

---
title: JWT Authentication and Refresh Token in Android with Retrofit Interceptor & Authenticator
published: true
description: In this article, we’ll be implementing JWT (JSON Web Token) authentication and silent refresh with Retrofit Interceptor and Authenticator.
tags: android, kotlin, okhttp
cover_image: https://cdn-images-1.medium.com/max/2560/1*K20haelxHIHOlmqnF8wSAw.png
---
In this article, we’ll be implementing JWT (JSON Web Token) authentication and silent refresh with Retrofit Interceptor and Authenticator. Silent token refresh is necessary when token expires and response from the server is 401 Unauthorized. Instead of logging out the user, we’ll refresh token and continue the request.

### Table of Contents

* Getting Started

* API Endpoint & Models

* Retrofit Setup

* Token DataStore

* Helpers

* Repository & View Model

* Interceptor & Authenticator

* Hilt Module

* UI

## Prerequisites

We’ll use LiveData, Hilt, Retrofit, OkHttp and DataStore in this article, so it’s better you know how they work.

I’ll skip some parts of the Hilt setup, so if you want to see the source code, you can find the link at the bottom of this article.

## Getting Started

App level build.gradle file,
```gradle
//DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0"

//Retrofit
def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

//Hilt
def hilt_version = "2.44"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"

//OkHttp
def okhttp_version = "4.10.0"
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
```
Don’t forget to add Internet permission in AndroidManifest.xml,
```XML
<uses-permission android:name="android.permission.INTERNET" />
```
## API Endpoint & Models

I’ve created my own backend to test token authentication. You can check it from this link. [MrNtlu/JWT-Test-API (github.com)](https://github.com/MrNtlu/JWT-Test-API)

Also, if you want to run it locally, you can follow [these instructions](https://github.com/MrNtlu/JWT-Test-API). It’s very simple Rest API. Token will be refreshed in every 30 seconds and we’ll have to refresh the old token with the new one.

*Note: You can get timeout error while testing. It’s free tier on render.com so it has limitations. Please keep trying until you get the result successfully.*

We’ll need to create couple of request/response models for Rest API,

POST auth/login request,

```Kotlin
data class Auth(
    @SerializedName("email_address")
    val email: String,
    val password: String
)
```

POST auth/login and GET auth/refresh response,
```Kotlin
data class LoginResponse(
    @SerializedName("access_token")
    val token: String
)
```

GET user/info response,

*Please put them into different files. I’ve put them into one code block to make it easier to read.*
```Kotlin
data class UserInfoResponse(
    @SerializedName("data")
    val userInfo: UserInfo,
    val message: String
)
data class UserInfo(
    val _id: String,
    val email_address: String
)
```

Finally ErrorResponse ,
```Kotlin
data class ErrorResponse(
    val code: Int,
    val message: String
)
```
## Retrofit Setup

We’ll have two separate API Service files. First one is AuthApiService ,

```Kotlin
interface AuthApiService {
    @POST("auth/login")
    suspend fun login(
        @Body auth: Auth,
    ): Response<LoginResponse>

    @GET("auth/refresh")
    suspend fun refreshToken(
        @Header("Authorization") token: String,
    ): Response<LoginResponse>
}
```

Second one is MainApiService,

```Kotlin
interface MainApiService {
    @GET("user/info")
    suspend fun getUserInfo(): Response<UserInfoResponse>
}
```

That’s it for now, we’ll create the Retrofit instance later in this article.

## Token DataStore

When a user logged in and reopened the application, we’ll need a way to save that token and use it. DataStore will help us for this problem. We’ll save the token locally and use it when needed.

```Kotlin
class TokenManager(private val context: Context) {
    companion object {
        private val TOKEN_KEY = stringPreferencesKey("jwt_token")
    }

    fun getToken(): Flow<String?> {
        return context.dataStore.data.map { preferences ->
            preferences[TOKEN_KEY]
        }
    }

    suspend fun saveToken(token: String) {
        context.dataStore.edit { preferences ->
            preferences[TOKEN_KEY] = token
        }
    }

    suspend fun deleteToken() {
        context.dataStore.edit { preferences ->
            preferences.remove(TOKEN_KEY)
        }
    }
}
```

That’s it. There is nothing special. Just a small note, context.dataStore won’t work and you’ll see error. Don’t worry. We’ll add it later on Hilt Module part.

## Helpers

Before we start implementing Repository and View Model, we’ll create helper classes.

First ApiResponse , this will help us manage the API requests and UI state management. We’ll be able to show the necessary UI to the user and extract data easily.

```Kotlin
sealed class ApiResponse<out T> {
    object Loading: ApiResponse<Nothing>()

    data class Success<out T>(
        val data: T
    ): ApiResponse<T>()

    data class Failure(
        val errorMessage: String,
        val code: Int,
    ): ApiResponse<Nothing>()
}
```

Second is apiRequestFlow , this class will help us to make API calls on the [IO thread](https://developer.android.com/kotlin/coroutines/coroutines-adv) and emit the state. Currently the timeout is set to 20 seconds, you can change it depending on your needs.

```Kotlin
fun<T> apiRequestFlow(call: suspend () -> Response<T>): Flow<ApiResponse<T>> = flow {
    emit(ApiResponse.Loading)

    withTimeoutOrNull(20000L) {
        val response = call()

        try {
            if (response.isSuccessful) {
                response.body()?.let { data ->
                    emit(ApiResponse.Success(data))
                }
            } else {
                response.errorBody()?.let { error ->
                    error.close()
                    val parsedError: ErrorResponse = Gson().fromJson(error.charStream(), ErrorResponse::class.java)
                    emit(ApiResponse.Failure(parsedError.message, parsedError.code))
                }
            }
        } catch (e: Exception) {
            emit(ApiResponse.Failure(e.message ?: e.toString(), 400))
        }
    } ?: emit(ApiResponse.Failure("Timeout! Please try again.", 408))
}.flowOn(Dispatchers.IO)
```

Finally BaseViewModel, this class will help us catch and present errors on correct thread and cancel the job when necessary.

```Kotlin
open class BaseViewModel : ViewModel() {
    private var mJob: Job? = null

    protected fun <T> baseRequest(liveData: MutableLiveData<T>, errorHandler: CoroutinesErrorHandler, request: () -> Flow<T>) {
        mJob = viewModelScope.launch(Dispatchers.IO + CoroutineExceptionHandler { _, error ->
            viewModelScope.launch(Dispatchers.Main) {
                errorHandler.onError(error.localizedMessage ?: "Error occured! Please try again.")
            }
        }){
            request().collect {
                withContext(Dispatchers.Main) {
                    liveData.value = it
                }
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        mJob?.let {
            if (it.isActive) {
                it.cancel()
            }
        }
    }
}

interface CoroutinesErrorHandler {
    fun onError(message:String)
}
```

That’s it. Now we can start implementing Repository & ViewModel.

## Repository & View Model

Since we’ve already implemented apiRequestFlow , repository classes will be very short.

```Kotlin
class AuthRepository @Inject constructor(
    private val authApiService: AuthApiService,
) {
    fun login(auth: Auth) = apiRequestFlow {
        authApiService.login(auth)
    }
}
```
```Kotlin
class MainRepository @Inject constructor(
    private val mainApiService: MainApiService,
) {
    fun getUserInfo() = apiRequestFlow {
        mainApiService.getUserInfo()
    }
}
```

Same goes for view models, only thing that is extra is LiveData objects. Both view models extend BaseViewModel which we’ve created earlier.

```Kotlin
@HiltViewModel
class AuthViewModel @Inject constructor(
    private val authRepository: AuthRepository,
): BaseViewModel() {

    private val _loginResponse = MutableLiveData<ApiResponse<LoginResponse>>()
    val loginResponse = _loginResponse

    fun login(auth: Auth, coroutinesErrorHandler: CoroutinesErrorHandler) = baseRequest(
        _loginResponse,
        coroutinesErrorHandler
    ) {
        authRepository.login(auth)
    }
}
```
```Kotlin
@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepository: MainRepository,
): BaseViewModel() {

    private val _userInfoResponse = MutableLiveData<ApiResponse<UserInfoResponse>>()
    val userInfoResponse = _userInfoResponse

    fun getUserInfo(coroutinesErrorHandler: CoroutinesErrorHandler) = baseRequest(
        _userInfoResponse,
        coroutinesErrorHandler,
    ) {
        mainRepository.getUserInfo()
    }
}
```
Finally, we are going to create one more view model for Token.

```Kotlin
@HiltViewModel
class TokenViewModel @Inject constructor(
    private val tokenManager: TokenManager,
): ViewModel() {

    val token = MutableLiveData<String?>()

    init {
        viewModelScope.launch(Dispatchers.IO) {
            tokenManager.getToken().collect {
                withContext(Dispatchers.Main) {
                    token.value = it
                }
            }
        }
    }

    fun saveToken(token: String) {
        viewModelScope.launch(Dispatchers.IO) {
            tokenManager.saveToken(token)
        }
    }

    fun deleteToken() {
        viewModelScope.launch(Dispatchers.IO) {
            tokenManager.deleteToken()
        }
    }
}
```

In this view model, we have only 1 variable which is token live data object. When we initialize the view model, we are setting token value to tokenManager.token and listen for changes.

Whenever we call saveToken or deleteToken , token value will be updated on tokenManager.getToken().collect so we don’t have to worry about anything else.

That’s it! Now we can implement Interceptor and Authenticator.

## Interceptor & Authenticator

Interceptor is very simple,

```Kotlin
class AuthInterceptor @Inject constructor(
    private val tokenManager: TokenManager,
): Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val token = runBlocking {
            tokenManager.getToken().first()
        }
        val request = chain.request().newBuilder()
        request.addHeader("Authorization", "Bearer $token")
        return chain.proceed(request.build())
    }
}
```

We are getting the token from tokenManager by blocking the current thread until completion with [runBlocking](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html). After that, we add Authorization header to the ongoing request.

![[Interceptors — OkHttp (square.github.io)](https://square.github.io/okhttp/features/interceptors/)](https://cdn-images-1.medium.com/max/2000/1*WjKvjIIcG8aK2oEX2mmkqg@2x.png)

There is a small problem, what if token is empty or outdated? The solution is simple, Authenticator.
>  Performs either **preemptive** authentication before connecting to a proxy server, or **reactive** authentication after receiving a challenge from either an origin web server or proxy server.
>  When reactive authentication is requested by an origin web server, the response code is 401 and the implementation should respond with a new request that sets the “Authorization” header.
>  For more info, you can click [the link](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-authenticator/).

```Kotlin
class AuthAuthenticator @Inject constructor(
    private val tokenManager: TokenManager,
): Authenticator {

    override fun authenticate(route: Route?, response: Response): Request? {
        val token = runBlocking {
            tokenManager.getToken().first()
        }
        return runBlocking {
            val newToken = getNewToken(token)

            if (!newToken.isSuccessful || newToken.body() == null) { //Couldn't refresh the token, so restart the login process
                tokenManager.deleteToken()
            }

            newToken.body()?.let {
                tokenManager.saveToken(it.token)
                response.request.newBuilder()
                    .header("Authorization", "Bearer ${it.token}")
                    .build()
            }
        }
    }

    private suspend fun getNewToken(refreshToken: String?): retrofit2.Response<LoginResponse> {
        val loggingInterceptor = HttpLoggingInterceptor()
        loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
        val okHttpClient = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build()

        val retrofit = Retrofit.Builder()
            .baseUrl("https://jwt-test-api.onrender.com/api/")
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build()
        val service = retrofit.create(AuthApiService::class.java)
        return service.refreshToken("Bearer $refreshToken")
    }
}
```

*Authenticator will not intercept every time, it’s going to be reactive to response and react accordingly. So, if token is valid, authenticator won’t interfere but if token is not valid, it’ll intercept and refresh the token.*

Again, before we do anything we get the token with runBlocking. After it, we use getNewToken function to request new token with the old token then we are checking if we’ve retrieved the new token successfully.

If we couldn’t get new token, we use deleteToken function which will trigger TokenViewModel token live data and on UI part we’ll force user to logout.

If we’ve retrieved the new refresh token successfully, we’ll update the old token value with new token and make the request.

![Authenticator Flow](https://cdn-images-1.medium.com/max/2000/1*vu5IBWDQDgCGfYoV-eJn7g.png)

As you can see from the image, we make the request (1) but token is expired, so we make another request to auth/refresh for a new token and successfully retrieve it (2). After that we make the request to user/info and successfully fetch the data (3).

## Hilt Module
```Kotlin
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "data_store")

@Module
@InstallIn(SingletonComponent::class)
class SingletonModule {

    @Singleton
    @Provides
    fun provideTokenManager(@ApplicationContext context: Context): TokenManager = TokenManager(context)

    @Singleton
    @Provides
    fun provideOkHttpClient(
        authInterceptor: AuthInterceptor,
        authAuthenticator: AuthAuthenticator,
    ): OkHttpClient {
        val loggingInterceptor = HttpLoggingInterceptor()
        loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY

        return OkHttpClient.Builder()
            .addInterceptor(authInterceptor)
            .addInterceptor(loggingInterceptor)
            .authenticator(authAuthenticator)
            .build()
    }

    @Singleton
    @Provides
    fun provideAuthInterceptor(tokenManager: TokenManager): AuthInterceptor =
        AuthInterceptor(tokenManager)

    @Singleton
    @Provides
    fun provideAuthAuthenticator(tokenManager: TokenManager): AuthAuthenticator =
        AuthAuthenticator(tokenManager)

    @Singleton
    @Provides
    fun provideRetrofitBuilder(): Retrofit.Builder =
        Retrofit.Builder()
            .baseUrl("https://jwt-test-api.onrender.com/api/")
            .addConverterFactory(GsonConverterFactory.create())

    @Singleton
    @Provides
    fun provideAuthAPIService(retrofit: Retrofit.Builder): AuthApiService =
        retrofit
            .build()
            .create(AuthApiService::class.java)

    @Singleton
    @Provides
    fun provideMainAPIService(okHttpClient: OkHttpClient, retrofit: Retrofit.Builder): MainApiService =
        retrofit
            .client(okHttpClient)
            .build()
            .create(MainApiService::class.java)
}
```

This class doesn’t need much of an explanation but some small notes,

* Like I’ve mentioned earlier, we set the Context.dataStore extension at the top of this class.

* HttpLogginInterceptor which logs request and response information.

* Instead of providing Retrofit instance, I’ve providedRetrofit.Builder because AuthApiService won’t use OkHttpClient but MainApiService will. To make the separation I am passing only the Retrofit.Builder instance.

That’s it. Now we can implement UI.

## UI

We’ll have Login and Main fragments.
```Kotlin
@AndroidEntryPoint
class LoginFragment : Fragment() {
    private val viewModel: AuthViewModel by viewModels()
    private val tokenViewModel: TokenViewModel by activityViewModels()

    private lateinit var navController: NavController

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_login, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        navController = Navigation.findNavController(view)
        val loginTV = view.findViewById<TextView>(R.id.loginTV)
        
        tokenViewModel.token.observe(viewLifecycleOwner) { token ->
            if (token != null)
                navController.navigate(R.id.action_loginFragment_to_main_nav_graph)
        }

        viewModel.loginResponse.observe(viewLifecycleOwner) {
            when(it) {
                is ApiResponse.Failure -> loginTV.text = it.errorMessage
                ApiResponse.Loading -> loginTV.text = "Loading"
                is ApiResponse.Success -> {
                    tokenViewModel.saveToken(it.data.token)
                }
            }
        }

        view.findViewById<Button>(R.id.loginButton).setOnClickListener {
            viewModel.login(
                Auth("test@gmail.com", "123Test"),
                object: CoroutinesErrorHandler {
                    override fun onError(message: String) {
                        loginTV.text = "Error! $message"
                    }
                }
            )
        }
    }
}
```
*Views are poorly set, please ignore them.*

In LoginFragment, we have two view models, AuthViewModel and TokenViewModel. We’ve used activityViewModels because TokenViewModel should be scoped to Activity, not fragment.
>  activityViewModels() is pretty self-explanatory. It is used to scoped to its activity and when its activity get destroy, activityViewModels() will be destroyed as well. [Source Link](https://skynight1996.medium.com/navigation-component-comparison-between-viewmodels-activityviewmodels-and-ae0145734228).

We are observing two live data objects, token and loginResponse. When user clicks the loginButton and makes the viewModel.login request, we’ll retrieve the data.token from loginResponse and save it. When saved, token will observe and when it’s set and not null we’ll navigate to MainFragment.
```Kotlin
@AndroidEntryPoint
class MainFragment : Fragment() {
    private val viewModel: MainViewModel by viewModels()
    private val tokenViewModel: TokenViewModel by activityViewModels()

    private lateinit var navController: NavController
    private var token: String? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?): View {
        return inflater.inflate(R.layout.fragment_main, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        navController = Navigation.findNavController(view)

        tokenViewModel.token.observe(viewLifecycleOwner) { token ->
            this.token = token
            if (token == null)
                navController.navigate(R.id.action_global_loginFragment)
        }

        val mainTV = view.findViewById<TextView>(R.id.infoTV)
        viewModel.userInfoResponse.observe(viewLifecycleOwner) {
            mainTV.text = when(it) {
                is ApiResponse.Failure -> "Code: ${it.code}, ${it.errorMessage}"
                ApiResponse.Loading -> "Loading"
                is ApiResponse.Success -> "ID: ${it.data.userInfo._id}\nMail: ${it.data.userInfo.email_address}\n\nToken: $token"
            }
        }

        view.findViewById<Button>(R.id.infoButton).setOnClickListener {
            viewModel.getUserInfo(object: CoroutinesErrorHandler {
                override fun onError(message: String) {
                    mainTV.text = "Error! $message"
                }
            })
        }

        view.findViewById<Button>(R.id.logoutButton).setOnClickListener {
            tokenViewModel.deleteToken()
        }
    }
}
```
MainFragment is very similar to LoginFragment. On infoButton click, we make the request and observe it. When we receive the userInfoResponse we present it to infoTV text view.

Also, we are observing the tokenViewModel token live data, when token is set to null, that means we should log out and move back to login screen.

Let’s see the results:

![Result](https://cdn-images-1.medium.com/max/2000/0*cYK1g43u4bXGvTvR)

That’s it! I hope it was useful. 👋👋

### Full Code

[MrNtlu/Token-Authentication (github.com)](https://github.com/MrNtlu/Token-Authentication)

### Sources:

* [kotlin — add access token to retrofit request header and refresh it through refresh token — Stack Overflow](https://stackoverflow.com/questions/73258301/add-access-token-to-retrofit-request-header-and-refresh-it-through-refresh-token/73258302#73258302)

* [https://github.com/Giga99/MediumRepos/tree/main/interceptors](https://github.com/Giga99/MediumRepos/tree/main/interceptors)

* [android — Fragment not being restored from backstack, also Hilt recreating ViewModel with Fragment’s lifecycle — Stack Overflow](https://stackoverflow.com/questions/64985634/fragment-not-being-restored-from-backstack-also-hilt-recreating-viewmodel-with)

* [App Architecture: Data Layer — DataStore — Android Developers](https://developer.android.com/topic/libraries/architecture/datastore)

* [https://youtu.be/SJePkypZCzo](https://youtu.be/SJePkypZCzo)

* [Navigation Component — Comparison between viewModels(), activityViewModels() and navGraphViewModels() | by Teo | Medium](https://skynight1996.medium.com/navigation-component-comparison-between-viewmodels-activityviewmodels-and-ae0145734228)

You can contact me on,

* [LinkedIn](https://www.linkedin.com/in/burak-fidan/)

* [Github](https://github.com/MrNtlu)

* [Twitter](https://twitter.com/BurakFNtlu)