Sapan Diwakar

Software developer

Follow me on Twitter Check out my code on GitHub View some of my designs on Dribbble Take a look at my Linked In profile

Refresh OAuth tokens using Moya, RxSwift

A very common use case when working with OAuth is to refresh the auth token. One way could be to do it periodically. A much simpler way, although, is to try refresh the auth token when you see a 401 response for an authenticated user. This is very straightforward to hook in to your API calls, especially if you are using RxSwift. Moya makes it even simpler, but it really can be used even without Retrofit.

We will create an extension on Observable so we can chain this whenever we want to use auth token refreshing.

public extension ObservableType where E == Response {

    /// Tries to refresh auth token on 401 errors and retry the request.
    /// If the refresh fails, the signal errors.
    public func retryWithAuthIfNeeded() -> Observable<Response> {
        return retryWhen { (e: Observable<ErrorType>) in
            Observable.zip(e, Observable.range(start: 1, count: 3), resultSelector: { $1 }).flatMap { i in
                return MyMoyaProvider.request(.RefreshToken).filterSuccessfulStatusAndRedirectCodes().mapObject(Token).catchError { error in
                    if case Error.StatusCode(let response) = error  {
                        if response.statusCode == 401 {
                            // Logout
                            do {
                                try User.logOut()
                                AlertHelper.show(title: "Error", subTitle: "Please login to continue", actionButton: AlertButton(title: "OK", 
backgroundColor: Constants.Colors.Blue.regular))  
                            } catch _ {
                                log.warning("Failed to logout")
                            }
                        }
                    }
                    return Observable.error(error)
                }.flatMapLatest { token -> Observable<Token> in
                    do {
                        try token.saveInRealm()
                    } catch let e {
                        log.warning("Failed to save access token")
                        return Observable.error(e)
                    }
                    return Observable.just(token)
                }
            }
        }
    }
}

Let's try to break it into pieces. We have used retryWhen to allow us to retry the current observable if we detect an error. The Observable is zipped to retry the requests three times. If we haven't yet tried to refresh the token thrice, we try to refresh the auth token using the service which in turn notifies the subscriber of any further errors or success.

I use realm to store my auth tokens, but you can change it as per your persistence strategy.

Here's how the extension can be used on your authenticated calls which should automatically try to refresh the auth token.

MyMoyaProvider.request(.Projects(page)).filterSuccessfulStatusAndRedirectCodes().retryWithAuthIfNeeded().mapObject(AlbumList).subscribe {  
[unowned self]
/// …