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 Interceptor
coroutines 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 OkHttp
to Interceptor
intercept and modify the process of sending requests and receiving responses.
The implementation plan is as follows:
- Customize
Interceptor
and add headers for interfaces that require token verification. - Get the response and determine whether the token has expired.
- 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.
- After refreshing the token, resend the original request with the new token to obtain the response.
Interceptor
The 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.