Fitbit and OAuth 2.0 - ios

I'm trying to add fitbit integration into an iOS app. I'm surprised to find how difficult this is... I imagined there would be an iOS SDK.
In any case, I'm trying to pull the data via the web-based API. It uses OAuth 1 & 2.
I've tried both, but have made more progress with OAuth 2.0.
I'm currently using AFOAuth2Manager to connect. Unfortunately, it is not working well.
Using the demo code with my account info, I get the following error:
Request failed: unacceptable content-type: text/html
By adding:
OAuth2Manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
That error goes away, but a new one is presented:
JSON text did not start with array or object and option to allow
fragments not set.
Here is the code I am using to make the request:
AFOAuth2Manager *OAuth2Manager =
[[AFOAuth2Manager alloc] initWithBaseURL:baseURL
clientID:#"CLIENT_ID"
secret:#"CLIENT_SECRET"];
OAuth2Manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
[OAuth2Manager authenticateUsingOAuthWithURLString:#"/oauth2/authorize"
username:USER_NAME
password:USER_PASSWORD
scope:#"activity"
success: ^(AFOAuthCredential *credential) {
NSLog(#"Token: %#", credential.accessToken);
}
failure: ^(NSError *error) {
NSLog(#"Error: %#", error);
}];
I wanted to try adding NSJSONReadingAllowFragments. But have been unable to get that into the code without error.
//OAuth2Manager.responseSerializer.readingOptions = NSJSONReadingAllowFragments;

In my case, I appended the wrong path to the Base URL. For example, my path was "/oauth/access_token."

Related

How to do Access Token Request in Fitbit API Integration - iOS?

I did OAuth2 by using OAuth2 - https://github.com/trongdth/OAuth2-for-iOS ,
I successfully logged in and got response
{
"kOAuth_AccessToken" = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NDIzODY2NzEsInNjb3BlcyI6Indsb2Mgd3BybyB3bnV0IHdzbGUgd3NldCB3d2VpIHdociB3YWN0IHdzb2MiLCJzdWIiOiIzRFJQQzYiLCJhdWQiOiIyMjlROVEiLCJpc3MiOiJGaXRiaXQiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4iLCJpYXQiOjE0NDIzODMwNzF9.5vTYvUAuvMflnOw_7cc1nZoighhtUx4RU26-Q7SewzQ";
"kOAuth_AuthorizeURL" = "https://www.fitbit.com/oauth2/authorize";
"kOAuth_Callback" = "http://oauth-callback/fitbit";
"kOAuth_ClientId" = string;
"kOAuth_ExpiredDate" = "";
"kOAuth_RefreshToken" = 97ad2a073f40f21974c8d27cdb74523047f39e98cd2adcfd6f6cc3eb92522d53;
"kOAuth_Scope" = "activity heartrate location nutrition profile settings sleep social weight";
"kOAuth_Secret" = string;
"kOAuth_TokenURL" = "https://api.fitbit.com/oauth2/token";
}
Here - how can I get code URI parameter value in the callback URI?
According to Fitbit doc's ---
Access Token Request:-
When a user authorizes your application in the Authorization Code Grant flow, your application must exchange the authorization code for an access token. The code is only valid for 10 minutes.
Authorization Header:-
The Authorization header should be set to Basic followed by a space and a Base64 encoded string of your application's client id and secret concatenated with a colon.
I did this by making use of AFNetworking lib, Please have a look on code (header)-
-(AFHTTPSessionManager *)getSessionManager {
if (!self.sessionManager){
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = nil;
NSURL *url = [NSURL URLWithString:FITBIT_BASE_URL];
self.sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:url sessionConfiguration:sessionConfiguration];
}
// [self.sessionManager.requestSerializer setValue:Appdelegate.fitbitAuthorizationString forHTTPHeaderField:#"Authorization"];
[self.sessionManager.requestSerializer setAuthorizationHeaderFieldWithUsername:Appdelegate.fitbitOAuthClientId password:Appdelegate.fitbitOAuthClientSecret];
self.sessionManager.responseSerializer = [JSONResponseSerializerWithData serializer];
self.sessionManager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
return self.sessionManager;
}
and requested token access - code (parameter's) is
NSDictionary *tempParamsDict = #{#"client_id":[JsonUtil stringFromKey:#"kOAuth_ClientId" inDictionary:dictResponse], #"grant_type":#"authorization_code", #"redirect_uri":[JsonUtil stringFromKey:#"kOAuth_Callback" inDictionary:dictResponse], #"code":#""};
My request is
[dP postJsonContentWithUrl:#"oauth2/token" parameters:tempParamsDict completion:^(BOOL success, id responseObject, NSError *error) {
// NSLog(#"%#", responseObject);
if (success) {
NSLog(#"Response: %#", responseObject);
}
else {
NSLog(#"%#", error.localizedDescription);
/*
You got 404 response ,The 404 or Not Found error message is a HTTP standard response code indicating that the client was able to communicate with the server, but the server could not find what was requested.
Make sure that you have valid url, try to put your link in the browser and see if it is correct, if the url you requested have special headers
*/
}
}];
I am getting - Request failed: not found (404) error.
What have I missed and How can I proceed further to get token and access Fitbit APIs?
Update
That was happening due to access code so I tried to access code, now I am getting this error
description of error->_userInfo:
{
NSErrorFailingURLKey = "https://api.fitbit.com/oauth2/token.json";
NSErrorFailingURLStringKey = "https://api.fitbit.com/oauth2/token.json";
NSLocalizedDescription = cancelled;
}
Thanks a lot in advance
Instead of NSURLSession use NSURLConnection to request data from Fitbit APIs.

How to call a function in Microsoft Dynamics NAV to retrieve XML using AFNetworking2

I am an experienced iOS developer but have very basic knowledge of SOAP and various HTTP protocols.
I have a client who has created a Microsoft Dynamics NAV interface that will serve me some XML for an iOS app. I am using AFNetworking2 for the networking side and using NSXMLParser to parse the XML.
The parser works fine but I am struggling to get the XML data from the clients NAV url.
I believe I have successfully authenticated against the server but I can't seem to call the function that will return XML data for me.
I currently use this code to authenticate:
AFHTTPRequestOperationManager *manager= [AFHTTPRequestOperationManager manager];
NSURLCredential *credential = [NSURLCredential credentialWithUser:[AppController sharedAppController].authUserName
password:[AppController sharedAppController].authPassword
persistence:NSURLCredentialPersistenceForSession];
NSMutableURLRequest *request = [manager.requestSerializer requestWithMethod:#"POST"
URLString: #"http://xxx.xxx.xx.x:7056/DynamicsNAV_KPI/WS/Retail%20Company/Codeunit/Kiwi_KPI"
parameters:nil
error:nil];
manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
[manager.requestSerializer setValue:#"application/xml" forHTTPHeaderField:#"Accept"];
[manager.requestSerializer setValue:#"application/xml" forHTTPHeaderField:#"Content-Type"];
AFHTTPRequestOperation *authenticationOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[authenticationOperation setCredential:credential];
[authenticationOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Authorisation Success!");
NSLog(#"Response XML: %#", [operation responseString]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Authentication Failure: %#", error);
}];
This seems to get me in the door as the Authorisation success! message appears in the log. I am told by my client I now need to call the “GetData_KPI” with two parameters: “user” (SERVER\username) and an empty object/variable that should hold your return xml.
I created a what I believed was a SOAP Message for this:
NSString *soapMessage = [NSString stringWithFormat:#"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<soap:Envelope\n"
"xmlns:c=\"http://schemas.xmlsoap.org/soap/encoding/\"\n"
"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"\n"
"xmlns:n1=\"urn:microsoftdynamicsschemas/codeunit/Kiwi_KPI\">\n"
"<soap:Header></soap:Header>"
"<soap:Body>"
"<n1:GetDATA_KPI id=\"o0\" c:root=\"1\">"
"<n1:user>SERVER\\jdoe</n1:user>"
"<n1:kPIxml></n1:kPIxml>"
"</n1:GetData_KPI>"
"</soap:Body>"
"</soap:Envelope>"];
And added these 2 lines of code:
[request setHTTPBody: [soapMessage dataUsingEncoding:NSUTF8StringEncoding]];
[request addValue:soapMessage forHTTPHeaderField:#"SOAPAction"];
But when these 2 lines are added the authentication doesn't work and I get an error like this:
Authentication Failure: Error Domain=com.alamofire.error.serialization.response Code=-1011 "Request failed: unsupported media type (415)" UserInfo=0x188635f0 {com.alamofire.serialization.response.error.response=<NSHTTPURLResponse: 0x177ba0f0> { URL: http://xxx.xxx.xx.xx:7056/DynamicsNAV_KPI/WS/Retail%20Company/Codeunit/Kiwi_KPI } { status code: 415, headers {
"Content-Length" = 0;
Date = "Tue, 16 Jun 2015 21:19:55 GMT";
Server = "Microsoft-HTTPAPI/2.0";
} }, NSErrorFailingURLKey=http://xxx.xxx.xx.x:7056/DynamicsNAV_KPI/WS/Retail%20Company/Codeunit/Kiwi_KPI, NSLocalizedDescription=Request failed: unsupported media type (415), com.alamofire.serialization.response.error.data=<>}
My questions are:
1. How do I call the function GetDataKPI using AFNetworking?
How should the SOAP message be constructed to pass user name a nd a blank parameter for XML?
How will I access the blank parameter to get XML?
I really am at a loss with this stuff and would really appreciate any help.
Well I can only help you with one part of the question. As I can see your client used standard feature of Nav 2009 (or older) - web services.
Let's assume you have authorized successfully. Would get http 401 otherwise.
Next. Unsuported media... dunno what this is, but you can test weather your SOAP message is valid using any app that can post soap, like soapUI (see this answer). It will also help you create and send propper message by consuming wsdl of Nav web service. So you will see both request and response in xml format. If you end up getting http 500 see this answer.
How do I call the function GetDataKPI using AFNetworking?
That I can not help you with.
How should the SOAP message be constructed to pass user name and a blank parameter for XML?
Use sopaUI to build valid soap(xml) request. No such thing as blank parameter needed afaik. It is defined as output xml parameter in Nav but for you it will be just soap(xml) resposnse which you will get.
How will I access the blank parameter to get XML?
Parse HTTP response of your AFNetworking2 whatever this thing is =) Can only gues that it will be somwhere in responseObject.
If nothing helps try posting your web services wsdl here. May be it will clear things up.

Google OAuth Login Error: Invalid credentials

I have an iPad application which allows users to login to their Gmail account(s) using OAuth2. Thus far, the login process and email fetching is successful. However, when the app is closed and then re-opened after a (long) period of time, an error is produced "invalid credentials,' even though previous logins with the same credentials were successful.
Login Flow:
1) User logs in to gmail using OAuth 2.
2) User email address and oAuthToken provided by the GTMOAuth2Authentication object are saved to core data for future logins.
3) IMAP Session is created using saved email address and OAuthToken.
Here is the relevant code
Google Login
- (void)gmailOAuthLogin
{
NSDictionary *googleSettings = [[EmailServicesInfo emailServicesInfoDict] objectForKey:Gmail];
GTMOAuth2ViewControllerTouch *googleSignInController =
[[GTMOAuth2ViewControllerTouch alloc] initWithScope:GmailScope clientID:GmailAppClientID clientSecret:GmailClientSecret keychainItemName:KeychainItemName completionHandler:^(GTMOAuth2ViewControllerTouch *googleSignInController, GTMOAuth2Authentication *auth, NSError *error){
if (error != nil) {
//handle error
} else {
[[ModelManager sharedInstance] authenticateWithEmailAddress:[auth userEmail]
oAuthToken:[auth accessToken] imapHostname:[googleSettings objectForKey:IMAPHostName] imapPort:[[googleSettings objectForKey:IMAPPort]integerValue] smtpHostname:[googleSettings objectForKey:SMTPHostName] smtpPort:[[googleSettings objectForKey:SMTPPort]integerValue] type:EmailProtocolTypeImapAndSmtpGMail success:^(Account *account) {
//create IMAP session using above arguments
} failure:^(NSError *error) {
//handle error
}];
}
}];
[self presentGoogleSignInController:googleSignInController];
}
Create IMAP Session Using MailCore2
- (void)authenticateWithEmailAddress:(NSString *)emailAddress password:(NSString *)password oAuthToken:(NSString *)oAuthToken imapHostname:(NSString *)imapHostname imapPort:(NSInteger)imapPort smtpHostname:(NSString *)smtpHostname smtpPort:(NSInteger)smtpPort success:(void (^)())success failure:(void (^)(NSError *))failure
{
self.imapSession = [[MCOIMAPSession alloc] init];
self.imapSession.hostname = imapHostname;
self.imapSession.port = imapPort;
self.imapSession.username = emailAddress;
self.imapSession.connectionType = MCOConnectionTypeTLS;
self.imapSession.password = nil;
self.imapSession.OAuth2Token = oAuthToken;
self.imapSession.authType = nil != oAuthToken ? MCOAuthTypeXOAuth2 :
self.imapSession.authType;
[self.imapSession setConnectionLogger:^(void * connectionID, MCOConnectionLogType type,
NSData * data){
NSLog(#"MCOIMAPSession: [%i] %#", type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
self.smtpSession = [[MCOSMTPSession alloc] init];
self.smtpSession.hostname = smtpHostname;
self.smtpSession.port = smtpPort;
self.smtpSession.username = emailAddress;
self.smtpSession.connectionType = MCOConnectionTypeTLS;
self.smtpSession.password = nil;
self.smtpSession.OAuth2Token = oAuthToken;
self.smtpSession.authType = nil != oAuthToken ? MCOAuthTypeXOAuth2 :
self.smtpSession.authType;
[self.smtpSession setConnectionLogger:^(void * connectionID, MCOConnectionLogType type, NSData * data){
NSLog(#"MCOSMTPSession: [%i] %#", type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
[[self.imapSession checkAccountOperation] start:^(NSError *error) {
if (nil == error) {
success();
} else {
failure(error); //FAILS WITH INVALID CREDENTIALS ERROR
}
}];
}
Once again, the above code works fine, unless the application has not been used in some time. I was not sure if I needed to refresh the OAuthToken or not, so I tried doing the following on launch of the application:
GTMOAuth2Authentication *auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:KeychainItemName clientID:GmailAppClientID clientSecret:GmailClientSecret];
BOOL canAuthorize = [auth canAuthorize]; //returns YES
NSDictionary *googleSettings = [[EmailServicesInfo emailServicesInfoDict] objectForKey:Gmail];
[[ModelManager sharedDefaultInstance] authenticateWithEmailAddress:[auth userEmail] oAuthToken:[auth refreshToken] imapHostname:[googleSettings objectForKey:IMAPHostName] imapPort:[[googleSettings objectForKey:IMAPPort]integerValue] smtpHostname:[googleSettings objectForKey:SMTPHostName] smtpPort:[[googleSettings objectForKey:SMTPPort]integerValue] type:EmailProtocolTypeImapAndSmtpGMail success:^(Account *account) {
//create IMAP session
} failure:^(NSError *error) {
NSLog(#"failure %#", error);
}];
But I still get the same error. I have no idea why the OAuth token stops working or how to resolve this. Since the user is able to save multiple accounts, I am wondering if I need to save the refresh token for each account in core data and use that if the access token stops working?
(Disclaimer - I don't know iOS or the gtm-oauth2 libraries, but I do know Google's OAuth implementation.)
Conceptually you do need to persist the refresh token for the user. The refresh token is a long-lived credential which is used (along with your client secret) to get a short-lived access token that is used for actual API calls.
If you anticipate making multiple calls in a short period of time then your app will normally actually persist both the refresh token and access token (currently access tokens will last 1 hour).
That all said, it looks like the gtm-oauth2 library should be taking care of persisting these already (looks like authForGoogleFromKeychainForName does this).
What I think you need help with is getting an up-to-date access token that you can use to initiate your IMAP session.
The gtm-oauth2 library does contain an authorizeRequest method. It takes information about an HTTP request you intend to make and adds the appropriate authorization headers. It looks like this code will examine the state of the access token, and refresh it if necessary.
While I know you aren't able to make an HTTP request (you need to speak IMAP), my suggestion is to use this method with a dummy NSMutableURLRequest - and then, once it's finished, don't actually send the HTTP request, instead examine the headers it added and pull the access token from there.
See:
https://code.google.com/p/gtm-oauth2/wiki/Introduction#Using_the_Authentication_Tokens
Hope that helps - I don't have an iOS environment to test it on.

stackmob ios datastore http 401 error

I'm using the iOS DataStore API to upload data to StackMob. I get this error when I try to use my smclient initialized with my public key.
HTTP Code=401 "The operation couldn’t be completed. (HTTP error 401.)" UserInfo=0xa14dac0 {error=Insufficient authorization}
Sample code
[[self.smclient dataStore] createObject:eventDictObj
inSchema:#"EventSchema"
onSuccess:^(NSDictionary *object, NSString *schema)
{
NSLog(#"Created online event : %#", object);
successBlock();
}
onFailure:^(NSError *error, NSDictionary* object, NSString *schema)
{
failedBlock(error);
}];
And smclient is initialized as follows
self.smclient = [[SMClient alloc] initWithAPIVersion:#"0" publicKey:#"xxxxxxxxxx"];
For this use case I don't need to use the logged in user credentials to create this entry in StackMob
Make sure that the permissions are set to Open on your stack mob database.

How do I create a test user for a native iOS app?

I'm trying to test my native iOS app and I've been having huge problems with test users. Basically it seems that the normal graph based way of creating test users doesn't work for native apps. When I try I get the following response from the server:
{
"error": {
"message": "(#15) This method is not supported for native apps",
"type": "OAuthException",
"code": 15
}
}
This seems to be supported by various posts on SO and a page on the old FB forums:
http://forum.developers.facebook.net/viewtopic.php?id=93086
People say that the only way to create test users for native apps is to create proper fake accounts on FB, which is against FB terms and conditions. Is this really my only option ? I can't believe the FB devs cannot support test accounts for native apps.
Anyone know of any legitimate way to create native app test users ?
At the top of the developer documentation for test users, a GUI for maintaining test users is also mentioned.
In addition to the Graph API functionality described below for managing test users programmatically, there is also a simple GUI in the Developer App, available on your app's Roles page as shown in the screenshots below. It exposes all of the API functions described in this document.
For some reason I don't understand, the Graph API calls aren't available if your app has an 'App Type' (under Settings -> Advanced) of Native/Desktop. But you can use the GUI instead.
You can create a new facebook test user for your app by calling the below function.
Please user your APP ID (go to this link to get your App's ID:https://developers.facebook.com/apps)
Please go to your app on facebook and get the App secret ID
-(void)createNewUser{
NSString *appID=#"Past your App ID"
NSString *appSecret=#"Past your App secret"
NSDictionary *params = #{
#"true": #"installed",
#"access_token": #"appID|appSecret",
};
NSString *path = [NSString stringWithFormat:#"/appID/accounts/test-users"];
/* make the API call */
[FBRequestConnection startWithGraphPath:path
parameters:params
HTTPMethod:#"POST"
completionHandler:^(
FBRequestConnection *connection,
id result,
NSError *error
)
{
if (result && !error)
{
NSLog(#"Test-User created successfully: %#", result);
}
else
{
NSLog(#"Error creating test-user: %#", error);
NSLog(#"Result Error: %#", result);
}
}];
}
Please make sure you don't reveal your App secret ID to anyone and never include it in your app hardcoded like that.
Hope it helps
The following works for me using the Facebook iOS SDK v4.1.0:
// Listed as "App ID": https://developers.facebook.com/apps/
NSString *facebookAppId = #"1234_REPLACE_ME_5678";
// Listed as "App Token": https://developers.facebook.com/tools/accesstoken/
NSString *facebookAppToken = #"ABCD_REPLACE_ME_1234";
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc]
initWithGraphPath:[NSString stringWithFormat:#"/%#/accounts/test-users",
facebookAppId]
parameters:#{#"installed" : #"true"}
tokenString:facebookAppToken
version:#"v2.3"
HTTPMethod:#"POST"];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection,
NSDictionary *testUser,
NSError *facebookError) {
if (facebookError) {
NSLog(#"Error creating test user: %#", facebookError);
} else {
NSLog(#"access_token=%#", testUser[#"access_token"]);
NSLog(#"email=%#", testUser[#"email"]);
NSLog(#"user_id=%#", testUser[#"id"]);
NSLog(#"login_url=%#", testUser[#"login_url"]);
NSLog(#"password=%#", testUser[#"password"]);
}
}];
When successful, the output is then something like this:
access_token=CACQSQXq3KKYeahRightYouDontGetToLookAtMyAccessTokenspBkJJK16FHUPBTCKIauNO8wZDZD
email=aqhndli_huisen_1430877783#tfbnw.net
user_id=1384478845215781
login_url=https://developers.facebook.com/checkpoint/test-user-login/1384478845215781/
password=1644747383820
Swift4 version.
import FBSDKCoreKit
// App Token can be found here: https://developers.facebook.com/tools/accesstoken/
let appToken = "--EDITED--"
let appID = "--EDITED--"
let parameters = ["access_token": appID + "|" + appToken, "name": "Integration Test"]
let path = "/\(appID)/accounts/test-users"
guard let request = FBSDKGraphRequest(graphPath: path, parameters: parameters, httpMethod: "POST") else {
fatalError()
}
request.start { _, response, error in
if let error = error {
// Handle error.
return
}
if let response = response as? [AnyHashable: Any],
let userID = response["id"] as? String,
let token = response["access_token"] as? String,
let email = response["email"] as? String {
// Handle success response.
} else {
// Handle unexpected response.
}
}
You can use Facebook's Test User API https://developers.facebook.com/docs/test_users/ to create test users, just be sure to use the same App id.
Given a APP_ID and APP_SECRET, the following gives us an APP_ACCESS_TOKEN
curl -vv 'https://graph.facebook.com/oauth/access_token?client_id=APP_ID&client_secret=APP_SECRET&grant_type=client_credentials'
then we can use the token to create test users:
curl 'https://graph.facebook.com/APP_ID/accounts/test-users?installed=true&name=NEW_USER_NAME&locale=en_US&permissions=read_stream&method=post&access_token=APP_ACCESS_TOKEN
in the response body to this command will the new users' email and password.

Resources