Reputation: 1251
I need advices about how to handle the oauth2 token refreshing in my app.
I'm using GRPC to make my http requests to my API which I'm connected with an oauth2 token (it expires after 1 hour).
Before launching every request, I check the token:
Up-to-date ? launch networkRequest
Out-dated ? launch refreshToken -> launch networkRequest
Everything seems to work well but in some cases, my client "lose" the token.
The problem is that for 2 requests A & B launched in the exact same time, if the token is outdated, they will both refresh it. My server will generate a newTokenA, return it, generate newTokenB (remove newTokenA), return it. If the response newTokenA arrives to the client after newTokenB, the client token token will not be the good one.
I used a Semaphore to ensure that one call the refreshToken is made at the same time.
But when my Semaphore is waiting, I don't receive any response from my server.
let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
func authenticate(completion: (GRPCProtoCall) -> (Void)) -> GRPCProtoCall {
// Wait here if authenticate already called
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
// If token up to date
if isOAuth2TokenValid() {
dispatch_semaphore_signal(semaphore) // Release semaphore
completion(self.withAuthorization())
return self
}
// Refresh the outdated token
APIClient.shared.oAuth2AccessTokenRefresh { (response) -> (Void) in
dispatch_semaphore_signal(semaphore) // Release semaphore
completion(self.withAuthorization())
}
return self
}
Upvotes: 3
Views: 373
Reputation: 285220
Why not making the method entirely asynchronous? (self
seems to be known anyway)
func authenticate(completion: (GRPCProtoCall) -> ()) {
// If token up to date
if isOAuth2TokenValid() {
completion(self.withAuthorization())
} else {
// Refresh the outdated token
APIClient.shared.oAuth2AccessTokenRefresh { response in
completion(self.withAuthorization())
}
}
}
Upvotes: 0
Reputation: 443
I think your dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
holding your thread, you can try it with timeout incase last request does not response, and put it before return self
while semaphore.wait(timeout: DispatchTime.now() + Double(5000000000) / Double(NSEC_PER_SEC)) == DispatchTimeoutResult.success {//time out set to 5 seconds
print("wait")
}
Upvotes: 1
Reputation: 10209
First, I would suggest to create the semaphore for 1
resource, not for 0
(which is used for reading purposes):
let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
Secondly, I think the problem is that you first release the semaphore, and then call the completion block: Do something like this:
// Refresh the outdated token
APIClient.shared.oAuth2AccessTokenRefresh { (response) -> (Void) in
completion(self.withAuthorization())
dispatch_semaphore_signal(semaphore) // Release semaphore at very end
}
Upvotes: 0