class ApiAuthenticatorImpl @Inject constructor( @ApplicationContext private val context: Context, private val authController: AuthController, private val interactor: ApiAuthenticatorInteractor, private val logger: AppLogger, ) : Authenticator { /** * Returns a request that includes a credential to satisfy an authentication challenge in * [response]. Returns null if the challenge cannot be satisfied. * * The route is best effort, it currently may not always be provided even when logically * available. It may also not be provided when an authenticator is re-used manually in an * application interceptor, such as when implementing client-specific retries. */ override fun authenticate(route: Route?, response: Response): Request? { logger.d("[${javaClass.name}] :: Request needs authentication. Starting process.") synchronized(this) { // Get tokens. val accessToken = authController.getAccessToken() val refreshToken = authController.getRefreshToken() if (!accessToken.isNullOrEmpty() && response.request.header("Authorization") == null) { logger.d("[${javaClass.name}] :: Access token is valid. Adding as header.") return response.request.newBuilder() .header("Authorization", accessToken) .build() } else if (!refreshToken.isNullOrEmpty()) { logger.d("[${javaClass.name}] :: Access token is invalid. Refreshing token.") return runBlocking { // Try to refresh token. when (interactor.refreshToken()) { is AzureRefreshPartialState.Success -> { response.request.newBuilder() .header("Authorization", authController.getAccessToken() ?: "") .build() } is AzureRefreshPartialState.Failed -> { onCanNotAuthenticate(context, authController) null } } } } logger.e("[${javaClass.name}] :: Both access & refresh tokens are invalid.") // Can not satisfy challenge. onCanNotAuthenticate(context, authController) } return null } }