Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refresh token with SwiftUI Combine

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 :

  1. Make a request
  2. If the request fails with 401, refresh the auth token (which is another request)
    1. After the refresh token is done, retry the first request
    2. If it fails throw the error to be handled by the caller

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()

}
like image 240
Abdou Avatar asked Jan 13 '20 15:01

Abdou


People also ask

Does SwiftUI import combine?

In order for you to not have to import Combine , the SwiftUI framework would have to export Combine for you.

Can a user have multiple refresh tokens?

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.

Can I use JWT as refresh token?

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.

How do I refresh my access token?

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 .


1 Answers

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.

like image 98
Josh Homann Avatar answered Sep 20 '22 07:09

Josh Homann