Saturday 31 May 2014

Withings API declassified (IOS)

This blog as name suggests is about integrating Withings Api to Ios application.And why  am i writing this?because I  had very bad experience integrating this api.The documentation provided by Withings for this  API is very poor.Anyway lets go back to main topic.
1.For Accessing User’s body metrics through third party application we need to register the application.After successful  registration we will be provided with API key(CONSUMER_KEY
) and Api secrete(CONSUMER_SECRET)
2.Now for the coding part withing uses Oauth 1.0 authentication .For this step I started with “TDOauth” but soon realized that there are few bugs .So I used “sample-Oauth1” code written by Christian Hansen, thanks to him.This code needs little modifications to access Withings web services.here are those modified line of code below.
 At first  we need to change predefined variables in OAuth1Controller.m like this

#define OAUTH_CALLBACK       @"http://wbsapi.withings.net/user" //Sometimes this has to be the same as the registered app callback url
#define CONSUMER_KEY         @"4b197d27275d4bb34c28e2c19c4d4878be42e1d1d2363"
#define CONSUMER_SECRET      @"e16526c14e7783487e0b51ffc06645203b08e588df37e6e5172"
#define AUTH_URL             @"https://oauth.withings.com/"
#define REQUEST_TOKEN_URL    @"account/request_token"
#define AUTHENTICATE_URL     @"account/authorize"
#define ACCESS_TOKEN_URL     @"account/access_token"
#define API_URL              @http://wbsapi.withings.net/
#define OAUTH_SCOPE_PARAM    @""

#define REQUEST_TOKEN_METHOD @"POST"
#define ACCESS_TOKEN_METHOD  @"POST"

Now “In order to get a permanent access, the Consumer must get from the Service provider 3 items :
·         The user id of the User : unique identifier of the User in the Service provider systems (Withings)
·         The access token : the consumer will send this token when requesting protected data, it will identify the Consumer and will be check if this matches with the registered link between the User and the Consumer
·         The access token secret : the consumer will use it to sign every request, to authenticate itself but will never send it”

I am not going in detail about how the Oauth work and how the tempory request token can be used to get permanent access token as the “Simple Oauth1” is well-written and as Withings provide documentation(helpful or not) for it.
 So next modification in this page is to add a “Oauth callback” variable in the param list in the time of calling to get request token.The code Snippet is as below


+ (NSMutableDictionary *)standardOauthParameters
{
    NSString *oauth_timestamp = [NSString stringWithFormat:@"%lu", (unsigned long)[NSDate.date timeIntervalSince1970]];
    NSString *oauth_nonce = [NSString getNonce];
    NSString *oauth_consumer_key = CONSUMER_KEY;
    NSString *oauth_signature_method = @"HMAC-SHA1";
    NSString *oauth_version = @"1.0";
   
    NSMutableDictionary *standardParameters = [NSMutableDictionary dictionary];
    [standardParameters setValue:oauth_consumer_key     forKey:@"oauth_consumer_key"];
    [standardParameters setValue:oauth_nonce            forKey:@"oauth_nonce"];
    [standardParameters setValue:oauth_signature_method forKey:@"oauth_signature_method"];
    [standardParameters setValue:oauth_timestamp        forKey:@"oauth_timestamp"];
    [standardParameters setValue:oauth_version          forKey:@"oauth_version"];
    /////added by sankhadeep
    [standardParameters setValue:OAUTH_CALLBACK          forKey:@"oauth_callback"];
   
    return standardParameters;
}

***Things to remember is that we need to remove this “oauth_callback” variable when accessing user data,otherwise we will get an “Invalid parameter provided” status from Withings.

Next thing is to call the authentication method from the app page.For me i just defined this method(not to mentioned that i did necessary import previously)

/////////Withings  Authorization Calling

-(void)authorizeApp{
    LoginWebViewController *loginWebViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"loginWebViewController"];
   
    [self presentViewController:loginWebViewController
                       animated:YES
                     completion:^{
                        
                         [self.oauth1Controller loginWithWebView:loginWebViewController.webView completion:^(NSDictionary *oauthTokens, NSError *error) {
                            
                             if (!error) {
                                
                                 // Store your tokens for authenticating your later requests, consider storing the tokens in the Keychain
                                 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                                
                                 NSLog(@"self.oauthToken=%@,self.oauthTokenSecret",oauthTokens);
                                 self.oauthToken = oauthTokens[@"oauth_token"];
                                 self.oauthTokenSecret = oauthTokens[@"oauth_token_secret"];
                                 userId=oauthTokens[@"userid"];
           
                                 [defaults setObject:self.oauthToken forKey:@"oauth_token"];
                                 [defaults setObject:self.oauthTokenSecret forKey:@"oauth_token_secret"];
                                 [defaults setObject:userId forKey:@"userid"];
                                 [defaults synchronize];
                                 int indexNumber=0;
                               
                                
                                // NSLog(@"TTTT %@ %@ %@",[defaults objectForKey:@"oauth_token"],[defaults objectForKey:@"oauth_token_secret"],[defaults objectForKey:@"userid"]);
                                
                             }
                             else
                             {
                                 NSLog(@"Error authenticating: %@", error.localizedDescription);
                             }
                             [self dismissViewControllerAnimated:YES completion: ^{
                                 self.oauth1Controller = nil;
                               
                             }];
                         }];
                     }];
   
}
/////////////////////////////////////////////

- (OAuth1Controller *)oauth1Controller
{
    if (_oauth1Controller == nil) {
        _oauth1Controller = [[OAuth1Controller alloc] init];
    }
    return _oauth1Controller;
}

This method will store the Access token after successful authentication into NSUserdefault.

This is the end of Authenication part.Now it is time to access User body metrics data.
Here is the sample calling for this ,but before calling withins service we need to make change to a method in OAuth1Controller.m  ,here we will remove the oauth_callback” parameters as it will a generate invalid Signature value.value. Withings expect all the Oauth related parameters except the "callback" one with every web service call.and the modified method will look like this :-

#pragma mark - Step 1 Obtaining a request token

- (void)obtainRequestTokenWithCompletion:(void (^)(NSError *error, NSDictionary *responseParams))completion
{
    NSString *request_url = [AUTH_URL stringByAppendingString:REQUEST_TOKEN_URL];
    NSString *oauth_consumer_secret = CONSUMER_SECRET;
   
    NSMutableDictionary *allParameters = [self.class standardOauthParameters];
    if ([OAUTH_SCOPE_PARAM length] > 0) [allParameters setValue:OAUTH_SCOPE_PARAM forKey:@"scope"];

    NSString *parametersString = CHQueryStringFromParametersWithEncoding(allParameters, NSUTF8StringEncoding);
   
    NSString *baseString = [REQUEST_TOKEN_METHOD stringByAppendingFormat:@"&%@&%@", request_url.utf8AndURLEncode, parametersString.utf8AndURLEncode];
    NSString *secretString = [oauth_consumer_secret.utf8AndURLEncode stringByAppendingString:@"&"];
    NSString *oauth_signature = [self.class signClearText:baseString withSecret:secretString];
    [allParameters setValue:oauth_signature forKey:@"oauth_signature"];
    /////modified by sankhadeep sending allparameter instead of param dictionary for Withings
    parametersString = CHQueryStringFromParametersWithEncoding(allParameters, NSUTF8StringEncoding);
   
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:request_url]];
    request.HTTPMethod = REQUEST_TOKEN_METHOD;
   
    NSMutableArray *parameterPairs = [NSMutableArray array];
    for (NSString *name in allParameters) {
        NSString *aPair = [name stringByAppendingFormat:@"=\"%@\"", [allParameters[name] utf8AndURLEncode]];
        [parameterPairs addObject:aPair];
    }
    NSString *oAuthHeader = [@"OAuth " stringByAppendingFormat:@"%@", [parameterPairs componentsJoinedByString:@", "]];
    [request setValue:oAuthHeader forHTTPHeaderField:@"Authorization"];
   
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                               NSString *reponseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                               completion(nil, CHParametersFromQueryString(reponseString));
                           }];
}

####And the actual calling for getting data for perticular user is:-
-(void)getUserDataFromWithings:(NSString*)deviceType{
   
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    self.oauthToken=[defaults objectForKey:@"oauth_token"];
    self.oauthTokenSecret=[defaults objectForKey:@"oauth_token_secret"];
    userId=[defaults objectForKey:@"userid"];
   // NSLog(@"UserId %@",userId);
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:@"getmeas" forKey:@"action"];
    [dict setObject:userId forKey:@"userid"];
    [dict setObject:deviceType forKey:@"devtype"];
    [dict setObject:@"500" forKey:@"limit"];
   // [dict setObject:@"1" forKey:@"meastype"];
           [dict setObject:[NSString stringWithFormat:@"%d",(int)[[NSDate date] timeIntervalSince1970]] forKey:@"enddate"];    NSLog(@"self.oauthToken=%@,self.oauthTokenSecret=%@",self.oauthToken,self.oauthTokenSecret);
    NSURLRequest *request =
    [OAuth1Controller preparedRequestForPath:@"measure"
                                  parameters:dict
                                  HTTPmethod:@"GET"
                                  oauthToken:self.oauthToken
                                 oauthSecret:self.oauthTokenSecret];
   
   
    NSLog(@"RRRRR %@",request.URL);
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response,
                                               NSData *data, NSError *connectionError)
     {
         if (data.length > 0 && connectionError == nil)
         {
             NSDictionary *withingsData = [NSJSONSerialization JSONObjectWithData:data
                                                                      options:0
                                                                        error:NULL];
             //NSLog(@"HHHHHH %@===", withingsData);
            if ([[withingsData valueForKey:@"status"] intValue]==0) {
           
            
             NSLog(@"HHHHHH %@===",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
            

           }
         }
     }];
}
The “withingsData” variable will contain user data.That is all about integrating Withings. 

Also don't forget to remove the oauth_callback key from allParameters in the method
#pragma mark build authorized API-requests
+ (NSURLRequest *)preparedRequestForPath:(NSString *)path
                              parameters:(NSDictionary *)queryParameters
                              HTTPmethod:(NSString *)HTTPmethod
                              oauthToken:(NSString *)oauth_token
                             oauthSecret:(NSString *)oauth_token_secret
{
    if (!HTTPmethod
        || !oauth_token) return nil;
    
    NSMutableDictionary *allParameters = [self standardOauthParameters];
     [allParameters removeObjectForKey:@"oauth_callback"];///removed as it is not necessary for withings Api
    
    allParameters[@"oauth_token"] = oauth_token;
    if (queryParameters) [allParameters addEntriesFromDictionary:queryParameters];
    
    NSString *parametersString = CHQueryStringFromParametersWithEncoding(allParameters, NSUTF8StringEncoding);
    
    NSString *request_url = API_URL;
    if (path) request_url = [request_url stringByAppendingString:path];
    NSString *oauth_consumer_secret = CONSUMER_SECRET;
    NSString *baseString = [HTTPmethod stringByAppendingFormat:@"&%@&%@", request_url.utf8AndURLEncode, parametersString.utf8AndURLEncode];
    NSString *secretString = [oauth_consumer_secret.utf8AndURLEncode stringByAppendingFormat:@"&%@", oauth_token_secret.utf8AndURLEncode];
    NSString *oauth_signature = [self.class signClearText:baseString withSecret:secretString];
    allParameters[@"oauth_signature"] = oauth_signature;
    
    NSString *queryString;
    
    
    NSLog(@"GGGGG %@",allParameters);
     if (allParameters) queryString = CHQueryStringFromParametersWithEncoding(allParameters, NSUTF8StringEncoding);
    //if (queryParameters) queryString = CHQueryStringFromParametersWithEncoding(queryParameters, NSUTF8StringEncoding);
    if (queryString) request_url = [request_url stringByAppendingFormat:@"?%@", queryString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:request_url]];
    request.HTTPMethod = HTTPmethod;
    
    NSMutableArray *parameterPairs = [NSMutableArray array];
    [allParameters removeObjectsForKeys:queryParameters.allKeys];
    for (NSString *name in allParameters) {
        NSString *aPair = [name stringByAppendingFormat:@"=\"%@\"", [allParameters[name] utf8AndURLEncode]];
        [parameterPairs addObject:aPair];
    }
    NSString *oAuthHeader = [@"OAuth " stringByAppendingFormat:@"%@", [parameterPairs componentsJoinedByString:@", "]];
    [request setValue:oAuthHeader forHTTPHeaderField:@"Authorization"];
    if ([HTTPmethod isEqualToString:@"POST"]
        && queryParameters != nil) {
        NSData *body = [queryString dataUsingEncoding:NSUTF8StringEncoding];
        [request setHTTPBody:body];
    }
    NSLog(@"GGGGRRRG %@",request);
    return request;

}


11 comments:

  1. This is great. I've had a lot of trouble trying to connect to withings and make a successful call and this is perfect. I also found this on stackOverflow and was overjoyed to find something to help me. I'm really trying to get this to work and i'm getting a bunch of errors. My problem comes in with the method you created. I was able to fix a few of the errors but some of them I admit I have no clue how to fix it. Is there a way to contact you or maybe get the source code so I can figure out where on earth I am going wrong? I'm really starting to get frustrated with trying to figure the Withings stuff out!

    ReplyDelete
  2. Thanks for your comment.I am glad that you have found the blog useful.Yes,you can have the source code of my demo project.I have added you to Google+.Also you can contact me through email,visit my blogger profile for that.

    ReplyDelete
    Replies
    1. awesome, thank you so much. How can I download it though? Where is the project at?

      Delete
    2. Here is the temporary link https://www.dropbox.com/s/9om65rabf22o9il/simple-oauth1-withings%20Done.zip

      Delete
    3. I have used your project and it is able to get the "measure" data i.e to cal this url (https://wbsapi.withings.net/measure?action=getmeas), but when i try to get the data from (https://wbsapi.withings.net/v2/measure?action=getactivity), it gives invalid param. Can you please help me to get the activity data for the withings.

      The code that i used to get data is as follows:

      -(IBAction)getNew:(id)sender{

      NSMutableDictionary *dict = [NSMutableDictionary dictionary];


      [dict setObject:@"getmeas" forKey:@"action"];


      NSString *path = @"measure";

      NSURLRequest *request =

      [OAuth1Controller preparedRequestForPath:path

      parameters:dict

      HTTPmethod:@"GET"

      oauthToken:self.oauthToken

      oauthSecret:self.oauthTokenSecret];

      NSLog(@"RRRRR %@",request.URL);

      [NSURLConnection sendAsynchronousRequest:request

      queue:[NSOperationQueue mainQueue]

      completionHandler:^(NSURLResponse *response,

      NSData *data, NSError *connectionError)

      {

      if (data.length > 0 && connectionError == nil)

      {

      NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:data

      options:0

      error:NULL];

      NSLog(@"HHHHHH %@===",greeting);

      }


      }];

      }
      This part is working just fine but if i change the path from , NSString *path = @"measure"; to NSString *path = @"v2/measure";(and also change the dict set object and forkey) it doesnot give back the activity measure. Please help.

      Delete
  3. hi, i saw you post on Withings API declassified (IOS), it works perfectly, but what to do after that. I mean how to get data from the sensors to your app. Can you give me some idea, that would be great help. Thankx

    ReplyDelete
  4. I have used your project and it is able to get the "measure" data i.e to cal this url (https://wbsapi.withings.net/measure?action=getmeas), but when i try to get the data from (https://wbsapi.withings.net/v2/measure?action=getactivity), it gives invalid param. Can you please help me to get the activity data for the withings.

    The code that i used to get data is as follows:

    -(IBAction)getNew:(id)sender{

    NSMutableDictionary *dict = [NSMutableDictionary dictionary];


    [dict setObject:@"getmeas" forKey:@"action"];


    NSString *path = @"measure";

    NSURLRequest *request =

    [OAuth1Controller preparedRequestForPath:path

    parameters:dict

    HTTPmethod:@"GET"

    oauthToken:self.oauthToken

    oauthSecret:self.oauthTokenSecret];

    NSLog(@"RRRRR %@",request.URL);

    [NSURLConnection sendAsynchronousRequest:request

    queue:[NSOperationQueue mainQueue]

    completionHandler:^(NSURLResponse *response,

    NSData *data, NSError *connectionError)

    {

    if (data.length > 0 && connectionError == nil)

    {

    NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:data

    options:0

    error:NULL];

    NSLog(@"HHHHHH %@===",greeting);

    }


    }];

    }
    This part is working just fine but if i change the path from , NSString *path = @"measure"; to NSString *path = @"v2/measure";(and also change the dict set object and forkey) it doesnot give back the activity measure. Please help.

    ReplyDelete
  5. HHHHHH {
    error = "Invalid Params";
    status = 503; i get this error when trying to access user data as you mentioned, but if i comment out "oauth_callback" the app just gets stuck and is not redirected

    ReplyDelete
  6. Thank you Sankhadeep for the updated code, it works perfectly fine now, finally i can get the "getactivity" measure, you are a super star. Thank you once again for your quick responce

    ReplyDelete
  7. can you please post it with oauth 2.0 also?

    ReplyDelete