Skip to content

Commit 4b77651

Browse files
Merge pull request #439 from wisemen-digital/bugfix/refactoring-authenticator
Bugfix/refactoring authenticator
2 parents 3da2212 + afcf12b commit 4b77651

File tree

1 file changed

+70
-53
lines changed
  • data/networking/src/main/java/be/appwise/networking/interceptors

1 file changed

+70
-53
lines changed

data/networking/src/main/java/be/appwise/networking/interceptors/Authenticator.kt

Lines changed: 70 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ package be.appwise.networking.interceptors
22

33
import be.appwise.networking.NetworkConstants
44
import be.appwise.networking.Networking
5-
import be.appwise.networking.NetworkingUtil
5+
import be.appwise.networking.NetworkingUtil.responseCount
66
import be.appwise.networking.model.AccessToken
77
import com.orhanobut.logger.Logger
8-
import kotlinx.coroutines.runBlocking
98
import okhttp3.Authenticator
109
import okhttp3.Request
1110
import okhttp3.Response
@@ -16,77 +15,95 @@ class Authenticator(
1615
) : Authenticator {
1716
private var callsWithoutToken = 0
1817

19-
override fun authenticate(route: Route?, response: Response): Request? {
20-
val request = response.request
21-
Logger.t("Authenticator").d("Authenticate id: request: $request")
18+
private fun extractBearerToken(authHeader: String?): String? {
19+
return authHeader?.takeIf { it.startsWith("Bearer ", ignoreCase = true) }
20+
?.substring("Bearer ".length)
21+
}
2222

23+
override fun authenticate(route: Route?, response: Response): Request? {
24+
val originalRequest = response.request
25+
val requestUrl = originalRequest.url
26+
Logger.t("Authenticator").d("Authenticate triggered for $requestUrl")
2327

24-
// Get the current AccessToken
25-
val accessToken = Networking.getAccessToken()?.access_token
28+
val failedToken = extractBearerToken(
29+
originalRequest.header(NetworkConstants.HEADER_KEY_AUTHORIZATION)
30+
)
2631

27-
Logger.t("Authenticator").d("Accesstoken for $request token: $accessToken")
2832

29-
if (accessToken == null || accessToken.isEmpty()) {
33+
if (failedToken.isNullOrEmpty()) {
3034
// just try the call without
3135

3236
//TODO: there is still something wrong with the way we handle this...
3337
// Because the client is only created once in the application's lifecycle the counter is persisted...
3438
callsWithoutToken++
35-
return if (callsWithoutToken >= 2) null else request
39+
return if (callsWithoutToken >= 2) null else originalRequest
3640
}
3741

3842
// This block tries to refresh the token and then retry the call with the new token
3943
// the 'synchronized' means that when other calls try to refresh the token as well they will wait until the first refresh happened
4044
//
4145
// Look at http://tutorials.jenkov.com/java-concurrency/synchronized.html for more information
4246
synchronized(this) {
43-
// Logger.t("Authenticator").d("Synchronized, request: $request")
47+
val threadId = Thread.currentThread().id
48+
Logger.t("Authenticator").d("Synchronized [$threadId] Sync block entered for: $requestUrl")
4449

45-
// Get the current AccessToken, this might be different as the previous one because of the 'synchronized' method
46-
val newToken = Networking.getAccessToken()
47-
48-
Logger.t("Authenticator").d("Synchronized, request: $request, newtoken: ${newToken?.access_token}")
50+
val currentToken = Networking.getAccessToken()
51+
val currentAccessToken = currentToken?.access_token
4952

5053
// Check if the request was previously made as an authenticated request
51-
if (response.request.header(NetworkConstants.HEADER_KEY_AUTHORIZATION) != null) {
52-
// If the token has changed since the request was made, use the new token
53-
if (newToken?.access_token != accessToken) {
54-
Logger.t("Authenticator").d("Same tokens")
55-
56-
return request.newBuilder()
57-
.header(
58-
NetworkConstants.HEADER_KEY_AUTHORIZATION,
59-
"${newToken?.token_type} ${newToken?.access_token}"
60-
)
61-
.build()
62-
}
63-
64-
// In case this is the first time this line gets hit refresh the AccessToken
65-
val updatedToken = onRefreshToken(newToken.refresh_token ?: "")
66-
67-
Logger.t("Authenticator").d("Updated token: ${updatedToken?.access_token}")
68-
69-
if (updatedToken == null || NetworkingUtil.responseCount(response) >= 2) {
70-
// refresh failed , maybe you can logout user
71-
// returning null is critical here , because if you do not return null
72-
// it will try to refresh token continuously like 1000 times.
73-
// also you can try 2-3-4 times by depending you before logging out your user
74-
Networking.logout()
75-
} else {
76-
// Refreshing the token worked, save the new one
77-
Networking.saveAccessToken(updatedToken)
78-
79-
// Retry the request with the new token
80-
return request.newBuilder()
81-
.header(
82-
NetworkConstants.HEADER_KEY_AUTHORIZATION,
83-
"${updatedToken.token_type} ${updatedToken.access_token}"
84-
)
85-
.build()
86-
}
54+
if (response.request.header(NetworkConstants.HEADER_KEY_AUTHORIZATION) == null) return null
55+
56+
// If the token has changed since the request was made, use the new token
57+
if (currentAccessToken != failedToken) {
58+
Logger.t("Authenticator").i("[$threadId] Token already refreshed by another thread ($requestUrl). Current: $currentAccessToken, Failed with: $failedToken. Retrying with current token.")
59+
60+
return originalRequest.newBuilder()
61+
.header(
62+
NetworkConstants.HEADER_KEY_AUTHORIZATION,
63+
"${currentToken?.token_type} ${currentToken?.access_token}"
64+
)
65+
.build()
8766
}
88-
}
8967

90-
return null
68+
Logger.t("Authenticator").i("[$threadId] Token needs refresh ($requestUrl). Token that failed: $failedToken")
69+
70+
// check response count
71+
if (responseCount(response) > 1) {
72+
Logger.t("Authenticator").w("[$threadId] Retry limit (1 refresh attempt) reached for $requestUrl. Aborting refresh and logging out.")
73+
Networking.logout()
74+
return null
75+
}
76+
77+
// check if we have a refresh token
78+
val refreshToken = currentToken.refresh_token
79+
if (refreshToken.isNullOrBlank()) {
80+
Logger.t("Authenticator").e("[$threadId] No valid refresh token available for $requestUrl. Cannot refresh. Logging out.")
81+
Networking.logout()
82+
return null
83+
}
84+
85+
val updatedToken = onRefreshToken(refreshToken)
86+
87+
if (updatedToken == null) {
88+
// refresh failed , maybe you can logout user
89+
// returning null is critical here , because if you do not return null
90+
// it will try to refresh token continuously like 1000 times.
91+
// also you can try 2-3-4 times by depending you before logging out your user
92+
Logger.t("Authenticator").e("[$threadId] onRefreshToken failed for $requestUrl. Logging out.")
93+
Networking.logout()
94+
return null
95+
} else {
96+
Logger.t("Authenticator").i("[$threadId] Refresh SUCCESS for $requestUrl. Saving new token: ${updatedToken.access_token}. Retrying request.")
97+
Networking.saveAccessToken(updatedToken)
98+
99+
// Retry the request with the new token
100+
return originalRequest.newBuilder()
101+
.header(
102+
NetworkConstants.HEADER_KEY_AUTHORIZATION,
103+
"${updatedToken.token_type} ${updatedToken.access_token}"
104+
)
105+
.build()
106+
}
107+
}
91108
}
92109
}

0 commit comments

Comments
 (0)