I'm trying to implement a refresh token strategy in Swift 5 and the Combine Framework for iOS.
I don't plan on using any third party package, just using what is provided by the framework, `URLSession.dataTaskPublisher`, so mu goal is to :
This is a very trivial use case, but seems to be very hard to implement in Combine, that makes it really hard to use in any real life scenario.
Any help would be welcome !
This is my try, which unfortonately doesn't work
private func dataTaskPublisherWithAuth(for request: URLRequest) -> URLSession.DataTaskPublisher {
return session.dataTaskPublisher(for: request)
.tryCatch { error -> URLSession.DataTaskPublisher in
guard error.errorCode == 401 else {
throw error
}
var components = URLComponents(url: self.baseUrl, resolvingAgainstBaseURL: true)
components?.path += "/refresh"
components?.queryItems = [
URLQueryItem(name: "refresh_token", value: KeychainHelper.RefreshToken),
]
let url = components?.url
var loginRequest = URLRequest(url: url!)
loginRequest.httpMethod = "GET"
loginRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
return session.dataTaskPublisher(for: loginRequest)
.decode(type: LoginResponse.self, decoder: JSONDecoder())
.map { result in
if result.accessToken != nil {
// Save access token
KeychainHelper.AccessToken = result.accessToken!
KeychainHelper.RefreshToken = result.refreshToken!
KeychainHelper.ExpDate = Date(timeIntervalSinceNow: TimeInterval(result.expiresIn!))
}
return result
}
.flatMap { data -> URLSession.DataTaskPublisher in
session.dataTaskPublisher(for: request)
}
}.eraseToAnyPublsiher()
}
In order for you to not have to import Combine , the SwiftUI framework would have to export Combine for you.
With OAuth2 a clientID will only have 1 active set of tokens (access+refresh). If you have multiple ClientID's from the API you can define different scopes but then the user would have to authorize with all different applications (your client id's) and I am assuming that is not your use-case.
The JWT is used for accessing secure routes on the API and the refresh token is used for generating new JWT access tokens when (or just before) they expire.
These client credentials and the refresh_token can be used to create a new value for the access_token . To refresh the access token, select the Refresh access token API call within the Authorization folder of the Postman collection. Next, click the Send button to request a new access_token .
You should use the .tryCatch
method on Publisher here. This lets you replace an error with another publisher (such as replacing error 401 with a refresh request followed by a map
switchToLastest
auth request) or with another error (in this case if its not a 401 then just throw the original error).
Note that you probably shouldn't be using flatMap
here because its not the same as .flatMapLatest
in Rx or .flatmap(.latest)
in Reactive Swift. You want to get into the habit of using .map
and switchToLatest
in Combine (ie apple decided the flattening and the mapping are two separate operators). If you don't do this you will get into trouble in some places that generate more than one inner publisher, such as search while you type, because instead of getting the latests inner value you will get ALL of them, in arbitrary order since the network requests complete in indeterminate time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With