Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Issue for Twitter applications with callback URL set #79

Closed
albertodebortoli opened this Issue · 19 comments

10 participants

@albertodebortoli

If the Twitter application has a callback URL set, the authorization process got stuck in the view with the title "Select and Copy the PIN" and nothing can be done to complete the authentication. The view is blank.

I think the problem regards the modification of the method "locateAuthPinInWebView" in the last commit "New Javascript code for parsing for PIN results, from olamn2k." because it can't get the PIN.

Can you solve this please?

@bengottlieb
Owner

I just pulled down the latest and tried assigning a call-back URL to my app. It had no problems parsing. Does the demo app work for you?

@albertodebortoli

The demo app is not working with any Twitter app with a callback url set.
A friend of mine has just (maybe correctly) fixed the bug. There is no need for out-of-band/PIN authentication step in the flow in his opinion. I've replace the entire else branch in webViewDidFinishLoad in SA_OAuthTwitterEngine.m with this:

[_engine requestAccessToken];

    if ([_delegate respondsToSelector: @selector(OAuthTwitterController:authenticatedWithUsername:)])
      [_delegate OAuthTwitterController: self authenticatedWithUsername: _engine.username];
      [self performSelector: @selector(dismissModalViewControllerAnimated:) withObject: (id) kCFBooleanTrue afterDelay: 1.0];

or better
[self dismissModalViewControllerAnimated:YES];
since there is no reason to stare at the callback url page for 1 second.
No gotPin method needs to be called.

@bengottlieb
Owner

If you remove the callback, does it work? I can't this problem to repro here.

@albertodebortoli

Works like a charm to me now with and without callback URL set (in the application on http://dev.twitter.com).
I get the access_token, the refresh_token, the token_type and the expires_in keys in a JSON dictionary.
So I suppose the OAuth flow is quite done also without the OOB step, maybe weird...

@dvdh

I'm also having this issue, when the twitter app have a callback url set, login flow gets stuck at the "Select and Copy the PIN" page, this is on the iPhone 4.3 Simulator. When callback url is removed from the twitter app settings, login proceeds without any issue.

@bengottlieb
Owner

Yes, I think the callback URL confuses the issue, since it's trying to call out to an app.

@dvdh
@newacct

Other libraries like ShareKit allow you to specify the callback URL along with the key and secret, and it works perfectly.

@albertodebortoli

ShareKit is dead. Last commit on November 23, 2010.
Actually Twitter-OAuth-iPhone is the best choice out there.

@difficultashish

Having the same issue. The page redirects to the callback URL and the authenticatedWithUsername: method never gets called in the delegate.
I replaced the else code in webViewDidFinishLoad in SA_OAuthTwitterViewController (not in SA_OAuthTwitterEngine) like @albertodebortoli said and its working fine for me. Has this worked well for you @albertodebortoli so far?

@albertodebortoli

My patch worked for me, but I'm not so confident with OAuth to be sure that is correct.

@sek

+1 on alertodebortoll and difficultashish's solution:

This is an essential fix if you want to have other clients (who use callback urls as intended) to authenticate against the same Twitter App as your iOS app using Twitter-OAuth-iPhone

Here's what the entire method looks like for me now in SA_OAuthTwitterViewController.m (for anyone who wants to cut and paste a whole method)

- (void) webViewDidFinishLoad: (UIWebView *) webView {
    _loading = NO;
    //[self performInjection];
    if (_firstLoad) {
        [_webView performSelector: @selector(stringByEvaluatingJavaScriptFromString:) withObject: @"window.scrollBy(0,200)" afterDelay: 0];
        _firstLoad = NO;
    } else {
        // This else clause modified to work with twitter apps that have the callback URL set: https://dev.twitter.com/apps/
        // Bug details: https://github.com/bengottlieb/Twitter-OAuth-iPhone/issues/79
        [_engine requestAccessToken];

        if ([_delegate respondsToSelector: @selector(OAuthTwitterController:authenticatedWithUsername:)])
            [_delegate OAuthTwitterController: self authenticatedWithUsername: _engine.username];
        [self dismissModalViewControllerAnimated:YES];
    }

    [UIView beginAnimations: nil context: nil];
    _blockerView.alpha = 0.0;
    [UIView commitAnimations];

    if ([_webView isLoading]) {
        _webView.alpha = 0.0;
    } else {
        _webView.alpha = 1.0;
    }
}
@onederrsiva

@sek Working Seamlessly, Thank you so much.
BTW, when we enter wrong credentials, it dismisses the login view. Do you have any fix for this?
Thanks!

@lenhhoxung86

Hi everyone,
I used the patch above in my code and my app run well with callback url is set for a long time.
However, several days ago, I cannot login Twitter from my app. The patch seems to be inapplicable anymore. Does anyone know how to fix it?

@riyazgit

Hey Guys , its stop working for me also. Anyone knows how to fix it. Please reply early. My live app on appstore stop working.

@riyazahemad

@Alberto De Bortoli have any fix for this problem again. @bengottlieb Can you fix this probelm.

@lenhhoxung86

Finally, I found the solution:

  • 1st step: Check this repo: https://github.com/adriaant/OAuthWithCallback
  • 2nd: Modify method: - (BOOL) webView: shouldStartLoadWithRequest:navigationType: as in the above source
  • 3rd: Override class OAMutableURLRequest with methods and variable: @interface OAMutableURLRequestOverride : OAMutableURLRequest { NSMutableDictionary *extraOAuthParameters;//Added for additional parameter } //Override super method
  • (void)prepare; //Override super method
  • (void)setOAuthParameterName:(NSString)parameterName withValue:(NSString)parameterValue; @end Hope this help.
@riyazahemad

@lenhhoxung86 its not working can you please share sample code or some more details. Even if the repo you refer is not working with my api key and secret key with call back url.

@lenhhoxung86
  • Your callback URL should be valid, and may be you forgot to replace the const string (callback url) in the source of above repo.
  • In SA_OAuthTwitterController.m (Twitter_OAuth_CallbackURL is your callback URL):
  • Remove Else Branch in - (void) webViewDidFinishLoad: (UIWebView *) webView;
  • (BOOL) webView: (UIWebView *) webView shouldStartLoadWithRequest: (NSURLRequest *) request navigationType: (UIWebViewNavigationType) navigationType {

    //Added for url callback
    NSURL *requestURL = [request URL];

    if ([[requestURL host] isEqualToString:Twitter_OAuth_CallbackURL]) {
    [_engine performSelector:@selector(requestAccessToken:) withObject:[requestURL absoluteString] afterDelay:0.1];
    //Dismiss modal view controller here
    [self performSelector: @selector(dismissModalViewControllerAnimated:) withObject: (id) kCFBooleanTrue afterDelay: 1.0];
    return NO;
    }

    NSData *data = [request HTTPBody];
    char *raw = data ? (char *) [data bytes] : "";

    if (raw && strstr(raw, "cancel=")) {
    [self denied];
    return NO;
    }
    if (navigationType != UIWebViewNavigationTypeOther) _webView.alpha = 0.1;
    return YES;
    }

  • In SA_OAuthTwitterEngine.m:
    //Added for callback url

  • (void) requestAccessToken:(NSString *)callback {
    [self requestURL: self.accessTokenURL token: _requestToken onSuccess: @selector(setAccessToken:withData:) onFail: @selector(outhTicketFailed:data:) withCallback:callback];
    }

  • (void) requestURL: (NSURL *) url token: (OAToken *) token onSuccess: (SEL) success onFail: (SEL) fail withCallback:(NSString *)callback {
    OAMutableURLRequestOverride *request = [[[OAMutableURLRequestOverride alloc] initWithURL: url consumer: self.consumer token:token realm:nil signatureProvider: nil] autorelease];
    if (!request) return;

    if (self.pin.length) token.pin = self.pin;
    [request setHTTPMethod: @"POST"];

    NSArray *parts = [callback componentsSeparatedByString:@"?"];
    NSArray *pairs = [[parts objectAtIndex:1] componentsSeparatedByString:@"&"];
    for (NSString *pair in pairs) {
    NSArray *elements = [pair componentsSeparatedByString:@"="];
    [request setOAuthParameterName:[elements objectAtIndex:0] withValue:[elements objectAtIndex:1]];
    }

    OADataFetcher *fetcher = [[[OADataFetcher alloc] init] autorelease];
    [fetcher fetchDataWithRequest: request delegate: self didFinishSelector: success didFailSelector: fail];
    }

  • I also override class OAMutableURLRequest: File OAMutableURLRequestOverride.h #import "OAMutableURLRequest.h"

@interface OAMutableURLRequestOverride : OAMutableURLRequest
{
NSMutableDictionary *extraOAuthParameters;//Added for additional parameter
}
//Override super method

  • (void)prepare; //Override super method
  • (void)setOAuthParameterName:(NSString)parameterName withValue:(NSString)parameterValue; @end

File OAMutableURLRequestOverride.m:
//
// OAMutableURLRequestOverride.m
//
//

#import "OAMutableURLRequestOverride.h"
#import "NSURL+Base.h"

@implementation OAMutableURLRequestOverride

  • (void)setOAuthParameterName:(NSString)parameterName withValue:(NSString)parameterValue
    {
    assert(parameterName && parameterValue);

    if (extraOAuthParameters == nil) {
    extraOAuthParameters = [NSMutableDictionary new];
    }

    [extraOAuthParameters setObject:parameterValue forKey:parameterName];
    }

  • (void)prepare
    {
    // sign
    // Secrets must be urlencoded before concatenated with '&'
    // TODO: if later RSA-SHA1 support is added then a little code redesign is needed
    signature = [signatureProvider signClearText:[self _signatureBaseString]
    withSecret:[NSString stringWithFormat:@"%@&%@",
    [consumer.secret URLEncodedString],
    [token.secret URLEncodedString]]];

    // set OAuth headers
    NSString *oauthToken;
    if ([token.key isEqualToString:@""])
    oauthToken = @""; // not used on Request Token transactions
    else
    oauthToken = [NSString stringWithFormat:@"oauth_token=\"%@\", ", [token.key URLEncodedString]];

    NSMutableString *extraParameters = [NSMutableString string];

    // Adding the optional parameters in sorted order isn't required by the OAuth spec, but it makes it possible to hard-code expected values in the unit tests.
    for(NSString *parameterName in [[extraOAuthParameters allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
    [extraParameters appendFormat:@", %@=\"%@\"",
    [parameterName URLEncodedString],
    [[extraOAuthParameters objectForKey:parameterName] URLEncodedString]];
    }

    NSString *oauthHeader = [NSString stringWithFormat:@"OAuth realm=\"%@\", oauth_consumer_key=\"%@\", %@oauth_signature_method=\"%@\", oauth_signature=\"%@\", oauth_timestamp=\"%@\", oauth_nonce=\"%@\", oauth_version=\"1.0\"%@",
    [realm URLEncodedString],
    [consumer.key URLEncodedString],
    oauthToken,
    [[signatureProvider name] URLEncodedString],
    [signature URLEncodedString],
    timestamp,
    nonce,
    extraParameters];

    [self setValue:oauthHeader forHTTPHeaderField:@"Authorization"];
    }

  • (NSString *)_signatureBaseString
    {
    // OAuth Spec, Section 9.1.1 "Normalize Request Parameters"
    // build a sorted array of both request parameters and OAuth header parameters
    NSMutableArray *parameterPairs = [NSMutableArray arrayWithCapacity:(7 + [[self parameters] count])]; // 6 being the number of OAuth params in the Signature Base String

    [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_consumer_key" value:consumer.key] URLEncodedNameValuePair]];
    [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_signature_method" value:[signatureProvider name]] URLEncodedNameValuePair]];
    [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_timestamp" value:timestamp] URLEncodedNameValuePair]];
    [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_nonce" value:nonce] URLEncodedNameValuePair]];
    [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_version" value:@"1.0"] URLEncodedNameValuePair]];

    if (![token.key isEqualToString:@""]) {
    [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_token" value:token.key] URLEncodedNameValuePair]];
    }

    for (OARequestParameter *param in [self parameters]) {
    [parameterPairs addObject:[param URLEncodedNameValuePair]];
    }

    NSArray *sortedPairs = [parameterPairs sortedArrayUsingSelector:@selector(compare:)];
    NSString *normalizedRequestParameters = [sortedPairs componentsJoinedByString:@"&"];

    // OAuth Spec, Section 9.1.2 "Concatenate Request Elements"
    NSString *ret = [NSString stringWithFormat:@"%@&%@&%@",
    [self HTTPMethod],
    [[[self URL] URLStringWithoutQuery] URLEncodedString],
    [normalizedRequestParameters URLEncodedString]];

    return ret;
    }

  • (void)dealloc {
    [extraOAuthParameters release];
    [super dealloc];
    }
    @end

  • And some files in OAuthConsumer (NSURL+Base.h e.g) will need to replace by copying corresponding files from https://github.com/adriaant/OAuthWithCallback

  • The key thing is that we added parameters for OAMutableURLRequest as described in post: https://dev.twitter.com/docs/api/1/post/oauth/access_token
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.