Using retryWhen to update tokens based on http error code

rxswift retrywhen single
moya/rxswift
observable retry
rxmoya

I found this example on How to refresh oauth token using moya and rxswift which I had to alter slightly to get to compile. This code works 80% for my scenario. The problem with it is that it will run for all http errors, and not just 401 errors. What I want is to have all my other http errors passed on as errors, so that I can handle them else where and not swallow them here.

With this code, if I get a HttpStatus 500, it will run the authentication code 3 times which is obviously not what I want.

Ive tried to alter this code to handle only handle 401 errors, but it seem that no matter what I do I can't get the code to compile. It's always complaining about wrong return type, "Cannot convert return expression of type Observable<Response> to return type Observable<Response>" which makes no sense to me..

What I want: handle 401, but stop on all other errors

import RxSwift
import KeychainAccess
import Moya

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<E> {
    return self.retryWhen {
      (e: Observable<ErrorType>) in
      return Observable.zip(e, Observable.range(start: 1, count: 3), resultSelector: { $1 })
        .flatMap { i in
          return AuthProvider.sharedInstance.request(
            .LoginFacebookUser(
              accessToken: AuthenticationManager.defaultInstance().getLoginTokenFromKeyChain(),
              useFaceBookLogin: AuthenticationManager.defaultInstance().isFacebookLogin())
            )
            .filterSuccessfulStatusCodes()
            .mapObject(Accesstoken.self)
            .catchError {
              error in
              log.debug("ReAuth error: \(error)")
              if case Error.StatusCode(let response) = error {
                if response.statusCode == 401 {
                  // Force logout after failed attempt
                  log.debug("401:, force user logout")
                  NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
                }
              }
              return Observable.error(error)
            }.flatMapLatest({
              token -> Observable<Accesstoken> in
              AuthenticationManager.defaultInstance().storeServiceTokenInKeychain(token)
              return Observable.just(token)
            })
      }
    }
  }
}
Compilation Error

Which line has the compilation error? It seems to me that it would be this line:

.catchError {
    error in
    //...
    return Observable.error(error)  // is this the line causing the compilation error?
}

If so, it's probably because catchError is expecting the block to return an Observable<Response> with which it can continue in case of an error, and not an Observable<ErrorType>.

In either case, it helps to annotate your code with more types so that you can pinpoint problems like this, as well as help the Swift compiler, which often can't figure out these kinds of things on its own. So something like this would have helped you:

.catchError {
    error -> Observable<Response> in
    //...
    return Observable.error(error)  // Swift should have a more accurate and helpful error message here now
}

Note that I'm only showing you what the error is and how to get Xcode to give you better error messages. What you're trying to return still isn't correct.

Only retry on 401

I'm not sure why you're expecting this code to treat 401 differently (other than posting to the notification center and logging). As it is, you're catching the error, but you're always returning an Observable with an Error event at the end (return Observable.error(error)), so it will never retry.

To get 401 to retry, you should return an Observable from the retryWhen block, which will send a Next event (signifying that you want to retry). For all other status codes, that Observable should send an Error (as you're currently doing), which will signify that you don't want to retry, and that you'd like the error propagated.

So something like this:

.retryWhen { errorObservable -> Observable<ErrorType> in
    log.debug("ReAuth error: \(error)")
    if case Error.StatusCode(let response) = error where response.statusCode == 401 {
        log.debug("401:, force user logout")
        NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
        // If `401`, then return the `Observable<ErrorType>` which was given to us
        // It will emit a `.Next<ErrorType>`
        // Since it is a `.Next` event, `retryWhen` will retry.
        return errorObservable
    }
    else {
        // If not `401`, then `flatMap` the `Observable<ErrorType>` which
        // is about to emit a `.Next<ErrorType>` into
        // an `Observable<ErrorType>` which will instead emit a `.Error<ErrorType>`.
        // Since it is an `.Error` event, `retryWhen` will *not* retry.
        // Instead, it will propagate the error.
        return errorObservable.flatMap { Observable.error($0) }
    }
}

Using retryWhen to update tokens based on http error code , Using retryWhen to update tokens based on http error code The problem with it is that it will run for all http errors, and not just 401 errors. I found this example on How to refresh oauth token using moya and rxswift which I had to alter slightly to get to compile. This code works 80% for my scenario. This code works 80% for my scenario. The problem with it is that it will run for all http errors, and not just 401 errors.

When you catchError, if it isn't a 401 error, then you simply need to throw the error. That will send the error down the pipe.

Using retryWhen to update tokens based on http error code, I found this example on How to refresh oauth token using moya and rxswift which I had to alter slightly to get to compile. This code works 80% for my scenario. Developer Community for Visual Studio Product family. This site uses cookies for analytics, personalized content and ads. By continuing to browse this site, you agree to this use.

There's a different solution to solve this problem without using Observable. It's written on pure RxSwift and returns a classic error in case of fail.

The easy way to refresh session token of Auth0 with RxSwift and Moya

The main advantage of the solution is that it can be easily applicable for different services similar to Auth0 allowing to authenticate users in mobile apps.

OAuth 2.0 Simplified: A Guide to Building OAuth 2.0 Servers, If you're using a JSON-based API, then it will likely return a JSON error In any case, the WWW-Authenticate header will also have the invalid_token error code. HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer error="invalid_token" error, it can then make a request to the token endpoint using the refresh token it  This issuance license file or Smartcard certificates are only needed when Token Based activation is being attempted. If you are not using Token Based Activation, this warning message can be safely ignored. This will not affect the ability to activate Windows using KMS host machines or when using MAK keys.

The Apache Modules Book: Application Development with Apache, be either an HTTP-date or an integer number of seconds (in decimal) after the time of Retry-After = "Retry-After" ":" ( HTTP-date | delta-seconds ) Two examples of its 14.38 Server The Server response-header field contains information about The product tokens are listed in order of their significance for identifying the  Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the two behaviours. However, some Web applications and frameworks use the 302 status code as if it were the 303. 303 See Other (since HTTP/1.1) The response to the request can be found under another URI using the GET method. When received in response to a POST (or PUT/DELETE), the client should presume that the server has received the data and should issue a new GET request to the given URI.

RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1, HTTP has been in use by the World-Wide Web global information initiative since 1990. the protocol referred to as "HTTP/1.1", and is an update to RFC 2068 [33]​. gateway, or tunnel, switching behavior based on the nature of each request. or error code, followed by a MIME-like message containing server information,  InvalidAssertion - Assertion is invalid because of various reasons - The token issuer doesn't match the api version within its valid time range -expired -malformed - Refresh token in the assertion is not a primary refresh token.

HTTP Status Codes, HTTP status codes and how to use them in RESTful API or Web Services. Solution 4. Try Windows Update Troubleshooter. The Windows Update Troubleshooter is always a method to consider when you have any problems running Windows Update. Go to the Microsoft official website, search for "Troubleshooter" and download it, and this tool is able to automatically diagnose and fix common problems with Windows Update.

Comments
  • Can you please describe why does it work. Moya providers create observable with request where Authorization header is already provided. Observable retry method retries the same request with old expired access token, isn't it?
  • Unfortunately I am unable to reproduce a working version of your answer. Where is the error variable coming from? Are there any changes related to Swift 3?
  • The error variable or Observable (in either catchError or retryWhen, respectively) is given to you as a block argument. You're chaining these methods onto an Observable, right? They're methods on Observable, not just free functions. As for Swift 3 changes, the only thing I noticed which has changed is the NSNotificationCenter line is now something like NotificationCenter.default.post(........
  • @solidcell Where does the error variable (Error.StatusCode(let response) = error) come from? The only block argument I see is the errorObservable one.
  • @brimstone the way I implemented it: .retryWhen({ (errorObservable) -> Observable<Error> in return errorObservable.enumerated().flatMap({ (attempts, error) -> Observable<Error> in guard (error as? MoyaError)?.response?.statusCode == 401, attempts < 2 else { return .error(error) } return Observable.just(error) }) })
  • However, that doesn't satisfy the case where it is a 401 and he wants to retry. Then he needs to send a .Next event so that retryWhen retries.
  • Where? He's always returning an Observable that emits .Error. He does nothing special Rx-related when it's a 401.