When the application and backend interact, some specific interfaces require token verification to return correct results, such as modifying user information, obtaining user information, etc. The token usually has a validity period set, and the results can be obtained normally only when used within the validity period. When the token expires, the user experience may be affected. You can automatically refresh the token after the token expires to optimize the user experience.

This article briefly introduces how to use Interceptorcoroutines to automatically refresh tokens.

Implement automatic refresh token

The application usually senses that the token has expired after failing to request an interface that requires verification of the token. In order to avoid affecting the user experience, you can use OkHttpto Interceptorintercept and modify the process of sending requests and receiving responses.

The implementation plan is as follows:

  1. Customize Interceptorand add headers for interfaces that require token verification.
  2. Get the response and determine whether the token has expired.
  3. If the token expires, initiate a request to refresh the token. If multiple interfaces that require token verification are executed concurrently, the operation of refreshing the token should be performed only once.
  4. After refreshing the token, resend the original request with the new token to obtain the response.

InterceptorThe sample code is as follows:

class ApiInterceptor : Interceptor {

    // 
    private val checkTokenInterface = arrayListOf(
        "/example/user/info,
        "/example/user/info/edit"
    )

    // 
    // 
    private var refreshingToken = AtomicBoolean(false)

    private val authHeaderKey = "token-key"
    
    // token
    private var currentToken = "tokenValue"

    override fun intercept(chain: Interceptor.Chain): Response {
        // 
        val checkToken = checkTokenInterface.contains(chain.request().url.encodedPath)
        // 
        var request = if (checkToken) {
            chain.request().newBuilder()
                .addHeader(authHeaderKey, currentToken)
                .build()
        } else {
            chain.request()
        }
        // 
        var response = chain.proceed(request)
        if (checkToken) {
            // 
            response.body?.let { responseBody ->
                try {
                    if (token) {
                        runBlocking {
                            // 
                            // 
                            if (request.header(authHeaderKey) == currentToken){
                                // 
                                if (refreshingToken.compareAndSet(false, true)) {                
                                    refreshToken(chain).takeIf { it.isNotEmpty() && it.isNotBlank() }?.let { newToken ->
                                        // 
                                        currentToken = newToken
                                    }
                                    // 
                                    refreshingToken.set(false)
                                }    
                            }
                            // 
                            // 
                            if (async { stopRefreshingToken() }.await()) {
                                // 
                                response = chain.proceed(chain.request().newBuilder()
                                    .addHeader(authHeaderKey, currentToken)
                                    .build())
                            }
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
        // 
        // 
        return response
    }

    private fun refreshToken(chain: Interceptor.Chain): String {
        // 
        val refreshTokenRequest = Request.Builder()
            .url("refresh token url")
            .addHeader(authHeaderKey, currentToken)
            .get()
            .build()
        return try {
            // token
            val refreshTokenResponse = chain.proceed(refreshTokenRequest)
            refreshTokenResponse.token
        } catch (e: IOException) {
            // 
            "" 
        }
    }

    private suspend fun stopRefreshingToken(): Boolean {
        return if (refreshingToken.get()) {
            // 
            delay(1000)
            stopRefreshingToken()
        } else {
            true
        }
    }
}

PS: Some of the code involves the company’s interface so it is omitted. In actual use, it needs to be adjusted appropriately according to the needs.

Leave a Reply

Your email address will not be published. Required fields are marked *