stream-chat-swift
stream-chat-swift copied to clipboard
Expired token not refreshed on startup
What did you do?
Connect a user, close the app and wait an hour, then restart the app. ChatClient.currentUserId
is set correctly from cache, but if I attempt to get channels (client.channelListController(query:)
) I get a 401 because of the expired token.
What did you expect to happen?
I thought the tokenProvider would be invoked to refresh the token. Alternatively I would expect to be able to call client.currentUserController.reloadUserIfNeeded
but that complains that I haven't called connectUser
yet.
What happened instead?
I get a 401 because of the expired token, and I have to manually fetch a token and invoke connectUser
.
GetStream Environment
GetStream Chat version: 4.14.0 GetStream Chat frameworks: StreamChat iOS version: 15.5 Swift version: 5 Xcode version: 13.4.1 Device: ipod touch 7th gen, ios 15.5
Additional context
It doesnt look like theres a way to detect an expired token other than manually storing and reading the JWT out of band from the ChatClient?
The specific error I get is
ServerErrorPayload(code: 2, message: "QueryChannels failed with error: "stream-authService-type missing or invalid"", statusCode: 401)).
Hi @J-Swift!
Can you update to the latest version of the SDK? There were a lot of improvements to token refreshing since the 14 version.
Let us know if you can still reproduce it in the latest 4.22.0 version.
Best, Nuno
OK I'll take a look and let you know.
Well I wouldn't say it fixed the issue so much as it changed the API to force me to use my workaround of manually logging in which avoids the issue I desribed. It looks like it calls the provided tokenProvider on every app start even if the token isnt expired.
Hi @J-Swift!
Can you share a snippet of how you implement the token provider so we can review it?
Thank you!
Its the most basic it can be I think
public protocol GetStreamTokenFetcher
{
func getToken() async -> String?
}
public class GetStreamProxy : NSObject {
enum MyError: Error {
case runtimeError(String)
}
let client: ChatClient;
let tokenProvider: TokenProvider
var initialized: Task<Bool, Never>? = nil
public var isLoggedIn: Bool {
get {
self.client.currentUserId != nil
}
}
public init(apiKey: String, tokenFetcher: GetStreamTokenFetcher) {
let config = ChatClientConfig(apiKey: APIKey(apiKey))
tokenProvider = { onComplete in
Task {
guard let token = await tokenFetcher.getToken() else {
onComplete(.failure(MyError.runtimeError("unable to fetch token")))
return
}
onComplete(.success(Token.init(stringLiteral: token)))
}
}
self.client = ChatClient.init(config: config)
super.init()
initialized = Task {
guard let userId = self.client.currentUserId else {
return false
}
let didLogin = await withCheckedContinuation({(continuation: CheckedContinuation<Bool, Never>) in
let userInfo = UserInfo.init(id: userId)
self.client.connectUser(userInfo: userInfo, tokenProvider: tokenProvider) { e in
if let e = e {
print(e)
continuation.resume(returning: false)
return
}
continuation.resume(returning: true)
}
})
return didLogin
}
}
}
Reading through https://github.com/GetStream/stream-chat-swift/pull/2031 it sounds like the "always refresh on initial connection" is by design.
I feel like theres an API disconnect on the currentUser
functionality when viewed against the description of the connectUser
API.
For example, if I login and then immediately close the app and reopen then self.client.currentUserId
and self.client.currentUserController().currentUser
are both non-nil. That heavily implies that I should be able to e.g. call self.client.channelListController(query: query)
, but that gives the 401 I outlined originally. The docs for currentUserController().currentUser
say
The currently logged-in user.
nil
if the connection hasn't been fully established yet, or the connection wasn't successful.
So I'm not sure if this should be nil
on startup, or if my previous token should be passed correctly without needing a separate connectUser
call.
Hi @J-Swift!
I would try using the token provider without async/await to make sure there's nothing wrong with the async/await integration. Either way, we are currently planning on improving the token refreshing logic, just like discussed on this ticket: https://github.com/GetStream/stream-chat-swift/issues/2255. We will improve this soon and we will get back to you as soon as possible.
Best, Nuno
Hi @J-Swift,
There seems to be an error on the documentation. This statement is not true:
if I login and then immediately close the app and reopen then self.client.currentUserId and self.client.currentUserController().currentUser are both non-nil. That heavily implies that I should be able to e.g. call self.client.channelListController(query: query), but that gives the 401 I outlined originally.
Having a currentUser/currentUserId doesn't mean the token is valid, it is just a representation of the user which is cached across sessions. Actually, Stream does not cache the token itself.
What's happening to you is that you might be querying the channels before connect call is completed, which basically means that there is no token, and the API call that is performed will of course return an error due to missing authentication.
We are working on improving that angle, but for now don't assume the token is there when currentUserId is not nil
Hi @J-Swift , the documentation has been updated to state what I shared above.