Refreshing token for userless oauth
saket opened this issue · 7 comments
I don't know if I understand this correctly, but it seems that when using userless oauth, access tokens do not get refreshed after they expire, resulting in 401 errors. I went through RedditClient#request()
, but it only renews token if AuthManager#canRenew()
returns true. And that only happens when a refresh token is present, which is not the case with userless credentials.
Am I missing something here?
Update: I just saw that AuthManager#canRenew()
is supposed to return true when using userless auth. I will investigate why I'm still seeing this error and report back.
Can you provide code that can reproduce this?
Sorry for the late update. I was trying to investigate this more, but I haven't found anything useful. I think this problem exists for both userless and logged-in auth modes. The refresh token becomes null occasionally, resulting in 401s.
When my app starts, my JRAW wrapper is setup this way:
accountHelper.onSwitch { newRedditClient -> clientSubject.onNext(newRedditClient) }
when {
tokenStore.usernames.isNotEmpty() -> accountHelper.switchToUser(tokenStore.usernames.first())
else -> accountHelper.switchToUserless()
}
Here's the way I use StatefulAuthHelper
:
class UserLoginHelper(private val helper: StatefulAuthHelper) {
fun authorizationUrl(): String {
val scopes = arrayOf(…)
return helper.getAuthorizationUrl(requestRefreshToken = true, useMobileSite = true, scopes = *scopes)
}
fun parseOAuthSuccessUrl(successUrl: String) {
helper.onUserChallenge(successUrl)
}
}
I also have an OkHttp interceptor that pro-actively refreshes tokens instead of waiting for 60m to complete.
override fun intercept2(chain: Interceptor.Chain): Response {
if (chain.request().url().toString().startsWith("https://www.reddit.com/api/v1/access_token")) {
return chain.proceed(chain.request())
}
if (client.authManager.canRenew()) {
val latestOAuthData: OAuthData = client.authManager.current!!
val expirationDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(latestOAuthData.expiration.time), UTC)
val recommendedRefreshDate = expirationDate.minusMinutes(10)
val now = LocalDateTime.now(UTC)
if (now > recommendedRefreshDate && now < expirationDate) {
refreshTokenAheadOfTime()
}
}
return chain.proceed(chain.request())
}
Do you see anything strikingly glaring in these gists? I'm trying but finding it difficult to reproduce this bug reliably, especially because I have to wait for 60m to complete.
Are you using a SharedPreferencesTokenStore or another subclass of DeferredPersistentTokenStore? If so you might have forgotten to load()
the tokens
No, I'm using a SharedPreferencesTokenStore
:
val store = SharedPreferencesTokenStore(appContext)
store.load()
store.autoPersist = true
I think I've figured out the problem with logged in users. I was getting the first username stored in a token store, which is wrong. It's usually <userless>
that is stored in the beginning before the logged in user's name.
For now, I'll store the logged-in username elsewhere and provide it to AccountHelper#switchToUser()
instead of finding it inside TokenStore#usernames
.
I'll also close this issue until I'm able to reproduce why access tokens aren't getting refreshed for userless apps.
Re-opening this issue. I'm still seeing this problem. After some limited research, I think that the refresh token in OAuthData
is somehow getting stored as null. If I read the stored json for PersistedAuthData
, I see this:
{
"latest": {
"accessToken": "XXX",
"scopes": [
"account",
"edit",
"history",
"identity",
"mysubreddits",
"privatemessages",
"read",
"report",
"save",
"submit",
"subscribe",
"vote",
"wikiread"
],
"expiration": 1528580965149
},
"refreshToken": "YYY"
}
Any ideas what might be happening?
I'm thinking of reading PersistedAuthData#getRefreshToken()
in the meanwhile in case OAuthData#getRefreshToken()
is null.
DeferredPersistentTokenStore won't write non-significant PersistedAuthData objects. Maybe there's some faulty logic in there?