8

I've been breaking my head over this the whole day.

I wish to integrate my iOS app with Withings api. It uses OAuth 1.0 and I can't seem to understand fully how to implement it.

I've been downloading multiple OAuth framworks (MPOAuth,gtm-oauth,ssoauthkit) but couldn't figure out completely what exactly I should do.

I searched a lot, also in stack overflow for good references on how to go about implementing OAuth 1.0 in general & integrating with Withings in particular with no success.

Kindly explain the flow of integrating an iOS app with an api that requires OAuth 1.0. Code examples would be very helpful. Suggested 3rd party frameworks would be nice too.

Just to clarify, I fully understand the OAuth 1.0 principles, I just have problems in actually implementing it in my app.

I think that a thorough answer with code examples and good references would be very helpful for lots of people as I couldn't find one. If anyone has good experience with implementing it, please take the time to share it.

David Ben Ari
  • 2,259
  • 3
  • 21
  • 40
  • Have you looked at [the example project included in gtm-oauth](https://github.com/jdg/gtm-oauth/tree/master/Examples/OAuthSample)? – bdesham Apr 10 '13 at 16:00
  • yes I have. These examples projects are really hard to navigate through and fully understand them.. – David Ben Ari Apr 10 '13 at 16:11
  • There is a LOT of bad OAuth code out there, particularly for iOS. So beware. The only decent one I ever encountered was Google's (gtm-oauth) so that receives a +1 from me. – Mike Weller Apr 10 '13 at 17:43
  • One of the problems in all those projects is that they all use ASIHttpRequest, which is not supported anymore. I recently switched my networking package in my app from ASI to AFNetworking, since ASI had a bug which caused a [crash](http://stackoverflow.com/questions/15186766/how-to-find-and-fix-the-cause-of-the-crash). – David Ben Ari Apr 11 '13 at 08:16

3 Answers3

13

TDOAuth in my opinion was the best solution. it is clean and simple, only one .h and .m file to work with, and no complicated example projects..

This is the OAuth 1.0 flow:

step 1 - get request token

//withings additional params
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:CALL_BACK_URL forKey:@"oauth_callback"];

//init request
NSURLRequest *rq = [TDOAuth URLRequestForPath:@"/request_token" GETParameters:dict scheme:@"https" host:@"oauth.withings.com/account" consumerKey:WITHINGS_OAUTH_KEY consumerSecret:WITHINGS_OAUTH_SECRET accessToken:nil tokenSecret:nil];

//fire request
NSURLResponse* response;
NSError* error = nil;
NSData* result = [NSURLConnection sendSynchronousRequest:rq  returningResponse:&response error:&error];
NSString *s = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
//parse result
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSArray *split = [s componentsSeparatedByString:@"&"];
for (NSString *str in split){
    NSArray *split2 = [str componentsSeparatedByString:@"="];
    [params setObject:split2[1] forKey:split2[0]];
}

token = params[@"oauth_token"];
tokenSecret = params[@"oauth_token_secret"];

step 2 - get authorize token (by loading the request in a UIWebView, the webViewDidFinishLoad delegate method will handle the call back..)

//withings additional params
NSMutableDictionary *dict2 = [NSMutableDictionary dictionary];
[dict setObject:CALL_BACK_URL forKey:@"oauth_callback"];

//init request
NSURLRequest *rq2 = [TDOAuth URLRequestForPath:@"/authorize" GETParameters:dict2 scheme:@"https" host:@"oauth.withings.com/account" consumerKey:WITHINGS_OAUTH_KEY consumerSecret:WITHINGS_OAUTH_SECRET accessToken:token tokenSecret:tokenSecret];

webView.delegate = self;
[DBLoaderHUD showDBLoaderInView:webView];
[webView loadRequest:rq2];

handle the webView as follow to initiate step 3 (I know the isAuthorizeCallBack smells a lot, but it does the job, should refactor it..)

- (void)webViewDidFinishLoad:(UIWebView *)aWebView
{
    [DBLoaderHUD hideDBLoaderInView:webView];

    NSString *userId = [self isAuthorizeCallBack];
    if (userId) {

        //step 3 - get access token
        [DBLoaderHUD showDBLoaderInView:self.view];
        [self getAccessTokenForUserId:userId];
    }

    //ugly patchup to fix an invalid token bug
    if ([webView.request.URL.absoluteString isEqualToString:@"http://oauth.withings.com/account/authorize?"])
    [self startOAuthFlow];
}

- (NSString *)isAuthorizeCallBack
{
    NSString *fullUrlString = webView.request.URL.absoluteString;

    if (!fullUrlString)
        return nil;

    NSArray *arr = [fullUrlString componentsSeparatedByString:@"?"];
    if (!arr || arr.count!=2)
        return nil;

    if (![arr[0] isEqualToString:CALL_BACK_URL])
        return nil;

    NSString *resultString = arr[1];
    NSArray *arr2 = [resultString componentsSeparatedByString:@"&"];
    if (!arr2 || arr2.count!=3)
        return nil;

    NSString *userCred = arr2[0];
    NSArray *arr3 = [userCred componentsSeparatedByString:@"="];
    if (!arr3 || arr3.count!=2)
        return nil;

    if (![arr3[0] isEqualToString:@"userid"])
        return nil;

    return arr3[1];
}

- (void)startOAuthFlow
{ 
    [self step1];
    [self step2];
}

and finally - step 3 - get access token

- (void)getAccessTokenForUserId:(NSString *)userId
{
    //step 3 - get access token

    //withings additional params
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:CALL_BACK_URL forKey:@"oauth_callback"];
    [dict setObject:userId forKey:@"userid"];

    //init request
    NSURLRequest *rq = [TDOAuth URLRequestForPath:@"/access_token" GETParameters:dict scheme:@"https" host:@"oauth.withings.com/account" consumerKey:WITHINGS_OAUTH_KEY consumerSecret:WITHINGS_OAUTH_SECRET accessToken:token tokenSecret:tokenSecret];

    //fire request
    NSURLResponse* response;
    NSError* error = nil;
    NSData* result = [NSURLConnection sendSynchronousRequest:rq  returningResponse:&response error:&error];
    NSString *s = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];

    //parse result
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    NSArray *split = [s componentsSeparatedByString:@"&"];
    for (NSString *str in split){
        NSArray *split2 = [str componentsSeparatedByString:@"="];
        [params setObject:split2[1] forKey:split2[0]];
    }

    [self finishedAthourizationProcessWithUserId:userId AccessToken:params[@"oauth_token"] AccessTokenSecret:params[@"oauth_token_secret"]];
}
David Ben Ari
  • 2,259
  • 3
  • 21
  • 40
  • I have implemented your code but I am getting stuck at step 3. I display the Withings login page in the webview but it always returns with "Invalid oath token". Am I missing something? Thanks in advance. – Dmorneault Jul 14 '13 at 18:51
  • @Dmorneault, I edited the answer with a small fix. look at the webViewDidFinishLoad method and add the code under the mark "ugly patchup to fix an invalid token bug". notice that it calls a method named "startOAuthFlow" which is simply step1 & step2 again. There seems to be a caching problem, and I tried resolving it with Withings support, but i ended up living this ugly but working patch.. – David Ben Ari Jul 15 '13 at 10:44
  • how did you create requests for interacting with the API after authentication? – Evan Davis Dec 14 '13 at 18:49
  • When working with API's such as Twitter, the request token has to be passed in as part of the URL, not the header, in order for it to work. – Supertecnoboff Mar 16 '17 at 14:11
  • Do you have sample code of this ? Please , i am doing the same job with ouath 1.0 – Jamshed Alam Jul 23 '18 at 03:20
3

I additionaly save request headers here

NSMutableDictionary *dict2 = [NSMutableDictionary dictionary];
[dict2 setObject:CALL_BACK_URL forKey:@"oauth_callback"];
NSURLRequest *rq2 = [TDOAuth URLRequestForPath:@"/authorize"
                                 GETParameters:dict2
                                        scheme:@"https"
                                          host:@"oauth.withings.com/account"
                                   consumerKey:WITHINGS_OAUTH_KEY
                                consumerSecret:WITHINGS_OAUTH_SECRET
                                   accessToken:self.token
                                   tokenSecret:self.tokenSecret];
headers = rq2.allHTTPHeaderFields;

And in callback method i will add missing parameters to the request. By doing it this way, i avoid "ugly patchup fix".

- (BOOL)webView:(UIWebView *)wV shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
if (![request.allHTTPHeaderFields objectForKey:@"Authorization"] &&
    [request.URL.absoluteString rangeOfString:@"acceptDelegation=true"].location == NSNotFound){
    NSMutableURLRequest *mutableCp = [request mutableCopy];
    NSLog(@"request :::%@", request);
    [mutableCp setAllHTTPHeaderFields:headers];
    dispatch_async(dispatch_get_main_queue(), ^{
        [webView loadRequest:mutableCp];
    });
    return NO;
}
return YES;
}

I hope it will help somebody

debris
  • 503
  • 6
  • 10
0

I would suggest you to check this project both as a reference and as a really working OAuth class. It inherits from another great project, so you you will need to add both in yours.Check if the license will suits your requirements. https://github.com/rsieiro/RSOAuthEngine

Andrea
  • 26,120
  • 10
  • 85
  • 131
  • If the project is outdated, is useless to downvote. At the time of the answer it worked perfectly ad I used in an app of mine with success. – Andrea Jul 18 '15 at 10:58