How to do Access Token Request in Fitbit API Integration - iOS? - 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.

Related

API Gateway generated SDK for iOS (Objective-C) with Cognito User Pool for authorized users

I have deployed an API using AWS API Gateway and I am trying to access it through an iOS device. Some endpoints support unauthorized users and I have no trouble accessing them but others don't and I can't manage to query them. I use the new Cognito User Pool feature for authentication which works fine when using a web application (or postman).
First, even though some of the endpoints are protected (like in the following picture of the console), when I deploy the API and generate the SDK for iOS (Objective-C), I can read in the README file: "All endpoints do not require authorization."
Then, when I run the following authentication code from AWS documentation, everything seems to work just fine:
AWSServiceConfiguration *serviceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionEUWest1 credentialsProvider:nil];
AWSCognitoIdentityUserPoolConfiguration *userPoolConfiguration = [[AWSCognitoIdentityUserPoolConfiguration alloc] initWithClientId:ClientId clientSecret:nil poolId:UserPoolId];
[AWSCognitoIdentityUserPool registerCognitoIdentityUserPoolWithConfiguration:serviceConfiguration userPoolConfiguration:userPoolConfiguration forKey:#"UserPool"];
AWSCognitoIdentityUserPool *pool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:#"UserPool"];
AWSCognitoIdentityUser *user = [pool getUser];
[[user getSession:username password:password validationData:nil] continueWithBlock:^id(AWSTask<AWSCognitoIdentityUserSession *> *task) {
if (task.error) {
NSLog(#"Could not get user session. Error: %#", task.error);
} else if (task.exception) {
NSLog(#"Could not get user session. Exception: %#", task.exception);
} else {
NSLog(#"Successfully retrieved user session data");
AWSCognitoIdentityUserSession *session = (AWSCognitoIdentityUserSession *) task.result;
NSMutableString *poolId = [[NSMutableString alloc] initWithString:#"cognito-idp.eu-west-1.amazonaws.com/"];
[poolId appendString:UserPoolId];
NSString *tokenStr = [session.idToken tokenString];
NSDictionary *tokens = [[NSDictionary alloc] initWithObjectsAndKeys:tokenStr, poolId, nil];
CognitoPoolIdentityProvider *idProvider = [[CognitoPoolIdentityProvider alloc] init];
[idProvider addTokens:tokens];
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionEUWest1 identityPoolId:IdentityPoolId identityProviderManager:idProvider];
[credentialsProvider clearKeychain];
[credentialsProvider clearCredentials];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionEUWest1
credentialsProvider:credentialsProvider];
[[credentialsProvider getIdentityId] continueWithBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
if (task.error) {
NSLog(#"Could not get identity id: %#", task.error);
} else if (task.exception) {
NSLog(#"Could not get identity id: %#", task.exception);
} else {
NSLog(#"Identity id: %#", task.result);
}
return nil;
}];
}
return nil;
}];
I implemented CognitoPoolIdentityProvider as specified in this post.
#interface CognitoPoolIdentityProvider ()
#property (strong, nonatomic) NSDictionary *tokens;
#end
#implementation CognitoPoolIdentityProvider
- (AWSTask<NSDictionary *> *)logins {
return [AWSTask taskWithResult:self.tokens];
}
- (void)addTokens:(NSDictionary *)tokens {
self.tokens = tokens;
}
#end
I manage to get a proper token (tested using postman) and a user id:
2016-12-27 12:43:35.760 AskHub[26625:10037234] AWSiOSSDK v2.4.11 [Debug] AWSURLResponseSerialization.m line:63 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response body:
{"IdentityId":"eu-west-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"}
2016-12-27 12:43:35.766 AskHub[26625:10037234] Identity id: eu-west-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
However, when I run the following code to hit a protected endpoint, CloudFront considers that I am not authenticated.
APIHealthClient *apiClient = [APIHealthClient defaultClient];
APIAskHubRequest *initReq = [[APIAskHubRequest alloc] init];
initReq.message = #"FIN";
NSLog(#"Sending initial request");
[[apiClient webhooksAskhubPost:initReq] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"Could not send initial request: %#", task.error);
} else if (task.exception) {
NSLog(#"Could not send initial request: %#", task.exception);
} else {
NSLog(#"Successfully sent initial request");
}
return nil;
}];
Reponse:
2016-12-27 12:56:25.247 AskHub[26784:10046562] Could not send initial request: Error Domain=com.amazonaws.AWSAPIGatewayErrorDomain Code=1 "(null)" UserInfo={HTTPBody={
message = Unauthorized;
}, HTTPHeaderFields={type = immutable dict, count = 9,
entries =>
3 : Via = {contents = "X.X XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.cloudfront.net (CloudFront)"}
4 : x-amzn-ErrorType = {contents = "UnauthorizedException"}
5 : Content-Type = {contents = "application/json"}
6 : Content-Length = {contents = "27"}
7 : Connection = {contents = "keep-alive"}
8 : x-amzn-RequestId = {contents = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"}
9 : Date = {contents = "Tue, 27 Dec 2016 11:56:25 GMT"}
10 : X-Cache = {contents = "Error from cloudfront"}
11 : X-Amz-Cf-Id = {contents = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}
}
}
Am I missing something here? Does the automatically generated SDK support User Pool authentication?
Based upon the code I see, you are clearing credentials but never getting credentials. On quick review it looks like you are setting up the service configuration and authentication correctly, but you never call credentials.
If that was the case, even though your application would authenticate, the credentials provider would never go get the logins dictionary.
(You can verify this in the AWS Console, if you look at the identityId you are trying to use to access the resource, you can look at the "logins" count. If it is 0, you are not logged in.)
In order to be authenticated you need to have the credentials provider call your identityProviderManager "logins" method.
That call happens when you do "getCredentialsForIdentity" which is "credentials" in the SDK.
Typically the sequence is GetID followed by GetCredentialsForId. In the IOS SDK, the GetId happens with "getSession" (so you should have that), and the GetCredentials happens with "credentials"
(Note that there are different SDKs with different naming in each (Mobile Hub vs IOS SDK for example) but all that matters is that your credentials provider gets a login dictionary. With verbose logging you should actually see the "logins" dictionary logged, which will prove to you that a logins dictionary is being provided to the credentials provider).
I figured out that there is a confusion here between two types of authentication modes: using a credentials provider or just adding the JWT token to the "Authorization" header parameter (or your custom parameter name if you specified one when creating your authorizer).
The first method did not work for me, even though it is the only one that I have found in the documentation when it comes to using the iOS SDK. The second method works pretty fine, but to be able to use it without hacking into the SDK, don't forget to add the "Authorization" header when you specify your API in API Gateway.

Azure Active Directory Refresh Token for iOS client

I have a standard iOS app from QuickStart downloaded from my portal like so:
Its a very basic iOS app that communicates with the Azure backend where you can push and pull data.
log in, log out
If you are to turn on authentication in your portal, you will need to log in. You do so like this:
[client loginWithProvider:#"windowsazureactivedirectory"
controller:controller
animated:YES
completion:^(MSUser *user, NSError *error) {
// save data into your keychain
// invoke various custom APIs
// be able to pull and push data to your Easy Table
}];
Log out is also simple:
[client logoutWithCompletion:^(NSError * _Nullable error) {
if(!error) {
[self clearCredentials];
complete(nil);
} else {
DDLogError(#"%#", [error debugDescription]);
complete(error);
}
}];
Refreshing the Token
My problem is the token. I understand that when using Active Directory, the token they give you lasts for 1 hour only. If you were to get a Refresh token, you need to use it within 14 days. If you do use it within 14 days, it will be good up to 90 days after that.
Hence, I waited an hour, and when I try using the client to pull data or invoke an API, it would give me an 401 error:
Error Domain=com.Microsoft.MicrosoftAzureMobile.ErrorDomain Code=-1301 "The server returned an error." UserInfo={com.Microsoft.MicrosoftAzureMobile.ErrorResponseKey= { URL: https://xxxx-xxxxxx.azurewebsites.net/api/getUserProfileData } { status code: 401, headers {
"Content-Length" = 0;
Date = "Tue, 25 Oct 2016 09:18:57 GMT";
Server = "Microsoft-IIS/8.0";
"Set-Cookie" = "ARRAffinity=743d3a9e04f3c081f27c2f2c3af95d099f93dc60f14553bd4423b0abc62cfd33;Path=/;Domain=xxxx-xxxxxx.azurewebsites.net";
"Www-Authenticate" = "Bearer realm=\"xxxx-xxxxxx.azurewebsites.net\"";
"X-Powered-By" = "ASP.NET";
} }, NSLocalizedDescription=The server returned an error., com.Microsoft.MicrosoftAzureMobile.ErrorRequestKey= { URL: https://xxxx-xxxxxx.azurewebsites.net/api/getUserProfileData }}
The log for my custom node script that the client hits, gives 401.71 unauthorized error:
2016-10-25T09:18:55 PID[35372] Warning JWT validation failed: IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '10/25/2016 09:12:31'
Current time: '10/25/2016 09:18:55'..
2016-10-25T09:18:55 PID[35372] Information Sending response: 401.71 Unauthorized
If I were to do a pull down on my table to grab data:
com.Microsoft.MicrosoftAzureMobile.ErrorRequestKey= { URL: https://xxx-xxxxx.azurewebsites.net/tables/TodoItem?$skip=0&$filter=(updatedAt%20ge%20datetimeoffset'2016-10-20T02%3A33%3A25.607Z')&$orderby=updatedAt%20asc&__includeDeleted=true&$top=50 }}
code - -1301
domain - com.Microsoft.MicrosoftAzureMobile.ErrorDomain
userInfo - {
NSLocalizedDescription = "The server returned an error.";
"com.Microsoft.MicrosoftAzureMobile.ErrorRequestKey" = " { URL: https://xxxx-xxxxxx.azurewebsites.net/tables/TodoItem?$skip=0&$filter=(updatedAt%20ge%20datetimeoffset'2016-10-20T02%3A33%3A25.607Z')&$orderby=updatedAt%20asc&__includeDeleted=true&$top=50 }";
Microsoft Azure has a small article on refreshing tokens here:
https://azure.microsoft.com/en-us/blog/mobile-apps-easy-authentication-refresh-token-support/
I would use refreshUserWithCompletion: method, but it keeps giving me 403 error.
Hence, if anyone have successfully refreshed their token on an iOS app, please comment.
thank you!
UPDATE: REFRESH WORKS, TOKEN STILL EXPIRES IN 1 HOUR
After following the links, my refreshUserWithCompletion method works, and returns a valid MSUser object with token. However, the issue is that this token is still only good for 1 hour. I'm pasting my refresh token code below. Please suggest, thanks!
if(!client.currentUser) {
[client loginWithProvider:#"windowsazureactivedirectory"
controller:controller
animated:YES
completion:^(MSUser * user, NSError *error) {
DDLogInfo(#"INITIAL TOKEN, userId: %#, token: %#", user.userId, user.mobileServiceAuthenticationToken);
if(!error && user) {
[client refreshUserWithCompletion:^(MSUser * _Nullable user, NSError * _Nullable error) {
// user object now has new token for us to use.
// I'm assuming this is the refresh token.
[self saveUserIntoKeyChain: user];
// I can just load the userId and token from the keychain for future uses, and they all work.
// The only problem is that this token is only good for 1 hour.
}];
}
}];
}
AAD requires configuration for refresh. See my blog on the subject: https://shellmonger.com/2016/04/13/30-days-of-zumo-v2-azure-mobile-apps-day-7-refresh-tokens/
If you haven't configured refresh, it isn't available. Once you do configure refresh, then the NEXT time you log in, you will get a refresh token that can be re-used.

Fitbit and OAuth 2.0

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

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.

iOS HTTP request - getting the error response message

This is my bit of code doing a GET request to a REST api.
Im not sure how to get back the message if I get an error:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSURL *URL = [NSURL URLWithString:urlString];
[request setURL:URL];
[request setHTTPMethod:#"GET"];
NSError *err = nil;
NSHTTPURLResponse *res = nil;
NSData *retData = [NSURLConnection sendSynchronousRequest:request returningResponse:&res error:&err];
if (err) // This part is never called.
{
NSLog(#"Error: %#", err);
}
else
{
if (res.statusCode != 200)
{
// show the user the status message
NSLog(#"Error: %#", res); // This part is called
}
else
{
}
}
I want to get the error message if it was not successful. But the if (err) block is never called. err is still null, although the statuscode is 400.
And if successful I will get back a json response.
In the code above I get back a statusCode of 400
The error block is not called because the error object is created only if a system level error occurs. This does not happen because the request is sent correctly and the server sends a response. If you are in control of the server, you should probably make it return status code 200 and include an app level status code in the response, that would tell your app that the entered credentials are incorrect.
Edit:
To get status message you can use
+ (NSString *)localizedStringForStatusCode:(NSInteger)statusCode
This is a class method of the NSHTTPURLResponse class.
if (res.statusCode != 200)
{
// show the user the status message
NSLog(#"Error: %#", [NSHTTPURLResponse localizedStringForStatusCode: res.statusCode]); // This part is called
}
Take a look at the NSError class reference:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/Reference/Reference.html
You can try to log the error message from the localizedDescription.
you are receiving this status code because- The Web server (running the Web site) thinks that the data stream sent by the client (e.g. your Web browser or our CheckUpDown robot) was 'malformed' i.e. did not respect the HTTP protocol completely. So the Web server was unable to understand the request and process it
to log above problem in respect to ios visit this link
If you read the documentation of sendSynchronousRequest...
error
Out parameter used if an error occurs while processing the request. May be NULL.
this mean that erro will be a valid NSError object in case there is a problem to resolve the request, like a malformed URL.
If the request can be resolved error will be NULL and according with HTTP protocol and depending to the server that you are trying to connect, the NSHTTPURLResponse object will contain all the information about the request.
In general is an error think that every status code different than 200 is an error, for example for a REST based API 204 mean empty data, and in this case the request is finished successfully but the requested resource is just empty data, and this is not an error.
So about your question, is absolutely fine that error is NULL most of the time, if is not mean that there is an issue before reach the target server, in general you have to consider both, error and according to the server that you are trying to talk the status code maps, in most of cases the REST pattern

Resources