Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intercept error in OAuth flow #768

Closed
sorenu opened this issue Aug 28, 2013 · 8 comments
Closed

Intercept error in OAuth flow #768

sorenu opened this issue Aug 28, 2013 · 8 comments
Labels

Comments

@sorenu
Copy link

@sorenu sorenu commented Aug 28, 2013

I am working against a backend which uses OAuth. I use a short-lived access token to request resources. When the token has expired I will receive an error response from the server. When receiving that error I must use a refresh token to send a request for a new access token. When I have the new token I can repeat the resource request.

My API calls are all RACSignals which I subscribe to to fetch resources. My problem is how to prevent the error received from the expired token request to propagate to the subscriber. In stead I want to intercept the error and send a new request to refresh the token and then use the new token to repeat the original request.

My current implementation looks something like this:

- (RACSignal *)fetchResource {
    RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
        // Do network request
        return nil;
    }];

    return [self tokenValidationWrapper:requestSignal];
}

- (RACSignal *)tokenValidationWrapper:(RACSignal *)requestSignal {
    [requestSignal subscribeError:^(NSError *error) {
        if (/*Error due to expired token*/) {
            [[self refreshToken] subscribeNext:^(id x) {
                // Send original request
                // Obviously this does not work because requestSignal has been terminated at this point
                [requestSignal subscribeNext:^(id x) {

                }];
            } error:^(NSError *err) {
                // Send error to subscriber of requestSignal
            }];
        } else {
            // Send error to subscriber of requestSignal
        }
    }];
    return [requestSignal replayLazily];
}

What would be the preferred way to implement this sort of conditional signal?

@joshaber
Copy link
Member

@joshaber joshaber commented Aug 28, 2013

This is such a great example for RAC. It'd be something like:

- (RACSignal *)tokenValidationWrapper:(RACSignal *)requestSignal {
    return [requestSignal catch:^(NSError *error) {
        // Catch the error, refresh the token, and then do the request again.
        return [[[self refreshToken] ignoreValues] concat:requestSignal];
    }];
}

So long as requestSignal is a cold signal (i.e., it does something on subscription, created using +createSignal: or +defer:), that -concat: will cause the request to be done again, after the token is refreshed.

@robb
Copy link
Member

@robb robb commented Aug 28, 2013

I'm totally going to steal this as an example for my talk 💖

@sorenu
Copy link
Author

@sorenu sorenu commented Aug 29, 2013

That works perfectly!

Thank you for your help and your great work with ReactiveCocoa.

@sorenu sorenu closed this Aug 29, 2013
@kkazuo
Copy link
Contributor

@kkazuo kkazuo commented Aug 29, 2013

Very impressive.
I had been wondering that why a signal invokes his block at every subscriptions.
I got the answer.
Thank you.

@neilpa neilpa mentioned this issue Sep 27, 2013
@sorenu
Copy link
Author

@sorenu sorenu commented Dec 2, 2013

@joshaber We just made a blog post about this issue. Have a look here: http://codeblog.shape.dk/blog/2013/12/02/transparent-oauth-token-refresh-using-reactivecocoa/

Once again, thanks for your help.

@SergeyBulyno
Copy link

@SergeyBulyno SergeyBulyno commented Feb 19, 2014

@joshaber, is this example should work if I use [RACSignal combineLatest:@[ [api fetchResource:a], [api fetchResource:b]]]; or use [api fetchResource:a] and [api fetchResource:a] to kick off multiple simultaneous requests and that they will all implicitly wait for the token to be refreshed?
Thank you in advance.

@askarimov
Copy link

@askarimov askarimov commented May 20, 2014

@sorenu, could you provide a sample code for [self refreshToken]. In my app, I am using your code from the blog, but cant do the request again with new access token. Thanks

@Doraemoe
Copy link

@Doraemoe Doraemoe commented Nov 20, 2015

I'm new to ReactiveCocoa, can anyone explain this code to me? In this code:

- (RACSignal *)tokenValidationWrapper:(RACSignal *)requestSignal {
    return [requestSignal catch:^(NSError *error) {
        // Catch the error, refresh the token, and then do the request again.
        return [[[self refreshToken] ignoreValues] concat:requestSignal];
    }];
}

why ignoreValues method is used? Moreover, to my understanding, this code will execute when an error want caught, and return a new signal that first do refreshtoken then do the orignal signal. However, what will this method return if no error was caught?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
7 participants
You can’t perform that action at this time.