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

Comments

Projects
None yet
7 participants
@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

This comment has been minimized.

Show comment
Hide comment
@joshaber

joshaber Aug 28, 2013

Member

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.

Member

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

This comment has been minimized.

Show comment
Hide comment
@robb

robb Aug 28, 2013

Member

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

Member

robb commented Aug 28, 2013

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

@sorenu

This comment has been minimized.

Show comment
Hide comment
@sorenu

sorenu Aug 29, 2013

That works perfectly!

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

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

This comment has been minimized.

Show comment
Hide comment
@kkazuo

kkazuo Aug 29, 2013

Contributor

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

Contributor

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 referenced this issue Sep 27, 2013

Closed

Feedback loop? #827

@sorenu

This comment has been minimized.

Show comment
Hide comment
@sorenu

sorenu 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.

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

This comment has been minimized.

Show comment
Hide comment
@SergeyBulyno

SergeyBulyno 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.

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

This comment has been minimized.

Show comment
Hide comment
@askarimov

askarimov 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

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

This comment has been minimized.

Show comment
Hide comment
@Doraemoe

Doraemoe 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?

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