Refresh access token automatically using AFOAuth2Manager - ios

I have a server with OAuth 2.0 implemented for issuing access and refresh tokens. The client for this server is an iOS App written in Objective-C. I am currently using AFNetworking 3.0 for HTTP requests and AFOAuth2Manager to handle authorization. I want to refresh my access token stored in iOS app using the refresh token issued by the server before the access token expires (server returns number of seconds to expire as { 'expires_in': 3600 } (one hour)). Everything is working fine until the access token expires. Below is my code for handling requests and authorization.
- (AFJSONRequestSerializer *)setRequestSerializer
{
AFJSONRequestSerializer *serializer = [AFJSONRequestSerializer serializer];
[serializer setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[serializer setValue:#"application/json" forHTTPHeaderField:#"Accept"];
User *currentUser = [User currentUser];
if (currentUser){
AFOAuthCredential *credentials = [AFOAuthCredential retrieveCredentialWithIdentifier:kEndpointServer];
if (!credentials.isExpired){
[serializer setAuthorizationHeaderFieldWithCredential:credentials];
}
}
return serializer;
}
- (AFJSONResponseSerializer *)setResponseSerializer
{
AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
return serializer;
}
- (AFSecurityPolicy *)setSecurityPolicy
{
NSString *certFilePath = [[NSBundle mainBundle] pathForResource:#"cert" ofType:#"cer"];
NSData *certData = [NSData dataWithContentsOfFile:certFilePath];
NSSet *pinnedCerts = [NSSet setWithObject:certData];
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:pinnedCerts];
[policy setAllowInvalidCertificates:YES]; // DEVELOPMENT ONLY
[policy setValidatesDomainName:NO];
return policy;
}
- (AFHTTPSessionManager *)sessionManager
{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy = [self setSecurityPolicy];
manager.requestSerializer = [self setRequestSerializer];
manager.responseSerializer = [self setResponseSerializer];
return manager;
}
- (AFOAuth2Manager *)OAuth2Manager
{
NSURL *baseURL = [NSURL URLWithString:kEndpointServer];
AFOAuth2Manager *manager = [[AFOAuth2Manager alloc] initWithBaseURL:baseURL clientID:kParamAPIClientId secret:kParamAPIClientSecret];
manager.securityPolicy = [self setSecurityPolicy];
return manager;
}
- (void)loginUser:(NSDictionary *)user block:(void (^)(BOOL, NSError *))result
{
// Set endpoint URL
NSString *loginEndpointURL = [NSString stringWithFormat:#"%#%#", kEndpointServer, kEndpointLogin];
AFHTTPSessionManager *manager = [self sessionManager];
if ([self internetConnectionAvailable]){
[manager POST:loginEndpointURL parameters:user progress:nil success:^(NSURLSessionDataTask *task, id responseObject){
NSDictionary *responseDict = (NSDictionary *)responseObject;
BOOL success = (BOOL)[(NSNumber *)[responseDict objectForKey:kParamSuccess] boolValue];
NSString *msg = (NSString *)[responseDict objectForKey:kParamMessage];
if (success){
// Get user
NSDictionary *userLoggedIn = (NSDictionary *)[responseDict objectForKey:kParamUser];
//NSLog(#"Logged in.");
NSString *tokenEndpointURL = [NSString stringWithFormat:#"/api%#%#", kEndpointOAuth, kEndpointToken];
OAuth2Manager *OAuth2Manager = [self OAuth2Manager];
[OAuth2Manager authenticateUsingOAuthWithURLString:tokenEndpointURL username:(NSString *)[user objectForKey:kParamEmail] password:(NSString *)[user objectForKey:kParamPassword] scope:nil success:^(AFOAuthCredential *credentials){
NSLog(#"Credentials:");
NSLog(#"Access Token: %#", credentials.accessToken);
NSLog(#"Refresh Token: %#", credentials.refreshToken);
// Store credentials
[AFOAuthCredential storeCredential:credentials withIdentifier:kEndpointServer];
// Set current user
[User setCurrentUser:userLoggedIn];
result(YES, nil);
}failure:^(NSError *error){
NSLog(#"Error authenticating user: %#", error);
result(NO, error);
}];
} else {
result(NO, [NSError errorWithDomain:msg code:kEDHTTPRequestFailedErrorCode userInfo:nil]);
}
}failure:^(NSURLSessionDataTask *task, NSError *error){
result(NO, error);
}];
} else {
result(NO, [NSError errorWithDomain:kEDNoInternetConnectionErrorDomain code:kEDNoInternetConnectionErrorCode userInfo:nil]);
}
}
I have found a similar question on SO:
How to automatically refresh expired token with AFOAuth2Manager?
But the problem with the answer given is that it is outdated (Works with AFNetworking 2.X.X, but does not work with AFNetworking 3.0).
What is the best practice for handling the refreshing of the access token automatically?

Related

working with the latest token sent in my web service

I have a problem to find the latest token .. back end make the token change in every request so if I use it once it turned to be invalid in the next time, and they send the next valid token in Authorization header.
I am trying to get the latest token.
+(void)askServerUsingToken:(void (^)(NSDictionary * json,bool isSuccess))completionBlock{
// NSString* urlString = [NSString stringWithFormat:#"%#%#",baseURL,action];
//check token
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *token = [defaults stringForKey:#"token"];
if (token!= nil) {
NSString *finalyToken = [[NSString alloc]initWithFormat:#"%#",token];
NSString *profile=[NSString stringWithFormat:#"%#profile?token=%#",baseURL,finalyToken];
NSLog(#"%#",profile);
AFHTTPSessionManager *manager=[AFHTTPSessionManager manager];
manager.responseSerializer=[AFJSONResponseSerializer serializer];
[manager.requestSerializer setValue:finalyToken forHTTPHeaderField:#"Authorization"];
[manager GET:profile parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject){
dispatch_async(dispatch_get_main_queue(), ^{
// NSError *errorJson=nil;
// NSDictionary *jsonList=[NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:&errorJson];
NSDictionary *jsonList=(NSDictionary*)responseObject;
// completionBlock(jsonList,true);
if (jsonList !=nil) {
completionBlock(jsonList,true);
}else{
completionBlock(jsonList,false);
}
});
}failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error){
dispatch_async(dispatch_get_main_queue(),^{
completionBlock(nil,false);
});
}];
}else{
NSLog(#"no token");
}`enter code here`
}

get authorization from Fitbit using Oauth in iOS

I want implement OAuth authentication for Fitbit to read the data from FitBit Api in my iOS app. I registered my app and i got clientId and client secret. I have been searched from past 2 days for tutorial, libraries. I am not any getting any idea about it. Please suggest me.
Note - According to https://dev.fitbit.com/docs/oauth2/
Applications should upgrade to OAuth 2.0 by March 14, 2016
Use safari or SFSafariViewController to open authorization page
Solution starts from here
please replace CLIENT_ID, REDIRECT_URI and other text to correct information
Point1-
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=activity%20nutrition%20heartrate%20location%20nutrition%20profile%20settings%20sleep%20social%20weight"]];
give proper scheme url, so that after successful login you will be redirected to your application. In openURL method you will get a OAUTHCODE
Point2-
Now get OAUTHTOKEN by using this OAUTHCODE
-(void)toGetRequestToken:(id)sender
{
NSString *strCode = [[NSUserDefaults standardUserDefaults] valueForKey:#"auth_code"];
NSURL *baseURL = [NSURL URLWithString:#"https://www.fitbit.com/oauth2/authorize"];
AFOAuth2Manager *OAuth2Manager = [AFOAuth2Manager managerWithBaseURL:baseURL clientID:CLIENT_ID secret:CONSUMER_SECRET];
OAuth2Manager.responseSerializer.acceptableContentTypes = [OAuth2Manager.responseSerializer.acceptableContentTypes setByAddingObject:#"text/html"];
NSDictionary *dict = #{#"client_id":CLIENT_ID, #"grant_type":#"authorization_code",#"redirect_uri":#"Pro-Fit://fitbit",#"code":strCode};
[OAuth2Manager authenticateUsingOAuthWithURLString:#"https://api.fitbit.com/oauth2/token" parameters:dict success:^(AFOAuthCredential *credential) {
// you can save this credential object for further use
// inside it you can find access token also
NSLog(#"Token: %#", credential.accessToken);
} failure:^(NSError *error) {
NSLog(#"Error: %#", error);
}];
}
Point3-
now you can hit other FitBit requests like for "UserProfile" --
-(void)getFitbitUserProfile:(AFOAuthCredential*)credential{
NSURL *baseURL = [NSURL URLWithString:#"https://www.fitbit.com/oauth2/authorize"];
AFHTTPSessionManager *manager =
[[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
[manager.requestSerializer setAuthorizationHeaderFieldWithCredential:credential];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:#"https://api.fitbit.com/1/user/-/profile.json"
parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSDictionary *dictResponse = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
NSDictionary *userDict =[dictResponse valueForKey:#"user"];
NSLog(#"Success: %#", userDict);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(#"Failure: %#", error);
}];
}

retry request when the internet connection is back - IOS

I am using AFNetworking 3.0 to perform Web request in my application.
Is there a way to automatically retry a request when the internet is back?
This is the request code:
#try {
NSString *urlMuniByGov = [NSString stringWithFormat:#"%#/%#", URL_MUNICIPALITES, selectedGov.govID];
NSURL *url = [NSURL URLWithString:urlMuniByGov];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.securityPolicy.allowInvalidCertificates = YES;
[manager GET:url.absoluteString
parameters:nil
progress:nil
success:^(NSURLSessionDataTask * task, id responseObject) {
NSArray *muniNSArray = [responseObject objectForKey:#"municipalites"];
if ([muniNSArray isKindOfClass:[NSArray class]]){
for (NSDictionary *dictionary in muniNSArray) {
Municipality *munModel = [Municipality new] ;
munModel.munID = [dictionary objectForKey:#"id"];
munModel.munNameAr = [[dictionary objectForKey:#"nom"] objectForKey:#"ar"];
munModel.munNameFr = [[dictionary objectForKey:#"nom"] objectForKey:#"fr"];
[self.munsArray addObject:munModel];
[self.munsString addObject:munModel.munNameAr];
}
}
[municipalityText setItemList:[NSArray arrayWithArray:self.munsString]];
} failure:^(NSURLSessionDataTask * task, NSError * error) {
NSLog(#"Error: %#", error);
}];
}
#catch (NSException *exception) {
NSLog(#"Exception: %#", exception);
}
[[AFNetworkReachabilityManager sharedManager]setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));}];
if any changes in the net connection this block will call , so here u can retry a request
for additional information follow the link https://github.com/AFNetworking/AFNetworking#network-reachability-manager

POST with URL parameters and JSON body in AFNetworking

I'd like to make a POST call that has both URL parameters and a JSON body:
URL http://example.com/register?apikey=mykey
JSON { "field" : "value"}
How can I use two different serializers at the same time with AFNNetworking? Here's my code with the URL parameters missing:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager POST:#"http://example.com/register" parameters:json success:^(AFHTTPRequestOperation *operation, id responseObject) {
I make a post method
/**
* Services gateway
* Method get response from server
* #parameter -> object: request josn object ,apiName: api endpoint
* #returm -> void
* #compilationHandler -> success: status of api, response: respose from server, error: error handling
*/
+ (void)getDataWithObject:(NSDictionary *)object onAPI:(NSString *)apiName withController:(UIViewController*)controller
:(void(^)(BOOL success,id response,NSError *error))compilationHandler {
controller = controller;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
// set request type to json
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// post request to server
[manager POST:apiName parameters:object success:^(AFHTTPRequestOperation *operation, id responseObject) {
// NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:responseObject
options:0
error:&error];
//NSString *JSONString = [[NSString alloc] initWithBytes:[jsonData bytes] length:[jsonData length] encoding:NSUTF8StringEncoding];
////
// check the status of API
NSDictionary *dict = responseObject;
NSString *statusOfApi = [[NSString alloc]initWithFormat:#"%#"
,[dict objectForKey:#"OK"]];
// IF Status is OK -> 1 so complete the handler
if ([statusOfApi isEqualToString:#"1"] ) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
compilationHandler(TRUE,responseObject,nil);
} else {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSArray *errorMessages = [responseObject objectForKey:#"messages"];
NSString *message = [errorMessages objectAtIndex:0];
[Utilities showAlertViewWithTitle:apiName message:message];
compilationHandler(FALSE,responseObject,nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSString *message = [NSString stringWithFormat:#"%#",[error localizedDescription]];
NSLog(#"Message is %#", message);
NSString *errorMessage = [NSString stringWithFormat:#"%#",[error localizedDescription]];
if (!([message rangeOfString:#"The request timed out."].location == NSNotFound)) {
[Utilities showAlertViewWithTitle:apiName message:errorMessage];
}
compilationHandler(FALSE,errorMessage,nil);
}];
// For internet reachibility check if changes its state
[self checkInternetReachibility:manager];
}
**for Example when we call the Service **
// calling service gateway API
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
"field",#"value",
nil];
[self getDataWithObject:dict onAPI:KGet_Preferences withController:(UIViewController*)controller :^(BOOL success, id response, NSError *error) {
if( success ) {
NSMutableDictionary *data = [[response valueForKey:#"data"] valueForKey:#"preferences"];
compilationHandler(success,data,error);
} else {
compilationHandler(success,nil,error);
}
}];
I believe there is no automatic way of doing it. However, there is a simple way of achieving it manually:
- (NSMutableURLRequest *)someRequestWithBaseURL:(NSString *)baseUrl
method:(NSString *)method
path:(NSString *)path
uriParameters:(NSDictionary *)uriParameters
bodyParameters:(NSDictionary *)bodyParameters
NSURL *url = [NSURL URLWithString:path relativeToURL:[NSURL URLWithString:baseUrl]];
AFHTTPRequestSerializer *httpRequestSerializer = [AFJSONRequestSerializer serializerWithWritingOptions:0]
NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithDictionary:bodyParameters];
if ([httpRequestSerializer.HTTPMethodsEncodingParametersInURI containsObject:method]) {
[parameters addEntriesFromDictionary:uriParameters];
} else {
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES];
// For urlEncodedString, check http://stackoverflow.com/a/718480/856549
urlComponents.percentEncodedQuery = [uriParameters urlEncodedString];
url = [urlComponents URL];
}
NSError *error;
NSURLRequest *request = [httpRequestSerializer requestWithMethod:method
URLString:[url absoluteString]
parameters:parameters
error:&error];

handling the Response with the AFNetworking 2

i am really new to IOS development. i want to develop an application which is dealing with some web services and display in a table view. somehow i found a 3rd party library for do the networking stuffs [AFNetworking 2]. below is my code to get the json response for any given url and parameters.
-(NSDictionary*)getWebServiceResponce:(NSString *)url :(NSDictionary *)object
{
// NSDictionary *parameters = [[NSDictionary alloc] initWithObjectsAndKeys:#"47", #"caregiverPersonId", nil];
__block NSDictionary* result=Nil;
__block NSString* person=Nil;
AFSecurityPolicy *policy = [[AFSecurityPolicy alloc] init];
[policy setAllowInvalidCertificates:YES];
AFHTTPRequestOperationManager *operationManager = [AFHTTPRequestOperationManager manager];
[operationManager setSecurityPolicy:policy];
operationManager.requestSerializer = [AFJSONRequestSerializer serializer];
operationManager.responseSerializer = [AFJSONResponseSerializer serializer];
[operationManager POST:url
parameters:object
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", [responseObject description]);
person = [responseObject[#"d"]objectForKey:#"PersonId"];
// [self returnedResponce:responseObject];
result = (NSDictionary *) responseObject[#"d"];
NSLog(#"RESULT: %#", result);
NSLog(#"personm: %#", person);
[operation waitUntilFinished];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [error description]);
//result = [error];
}
];
return result;
}
this code works perfectly.. but my point is when i put some breakpoints to check what are the values i got for several variables, it shows null. but my log shows the entire json response.
and i want to return my response object as a dictionary. because i want to do some process with the response.. can some one help me with this ?
The problem is that result is nil when it gets returned. AFNetworking uses ObjC's awesome blocks, they get executed asynchronously. Read more about it here.
You should include a callback block in your getWebServiceResponce method. I've thrown together a bit of code but you should really read more about blocks.
-(void)webServiceResponceForURL:(NSString *)url dictionary:(NSDictionary *)object success:(void (^)(NSDictionary *responseObject))success {
// NSDictionary *parameters = [[NSDictionary alloc] initWithObjectsAndKeys:#"47", #"caregiverPersonId", nil];
__block NSDictionary* result=Nil;
__block NSString* person=Nil;
AFSecurityPolicy *policy = [[AFSecurityPolicy alloc] init];
[policy setAllowInvalidCertificates:YES];
AFHTTPRequestOperationManager *operationManager = [AFHTTPRequestOperationManager manager];
[operationManager setSecurityPolicy:policy];
operationManager.requestSerializer = [AFJSONRequestSerializer serializer];
operationManager.responseSerializer = [AFJSONResponseSerializer serializer];
[operationManager POST:url
parameters:object
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", [responseObject description]);
person = [responseObject[#"d"]objectForKey:#"PersonId"];
// [self returnedResponce:responseObject];
result = (NSDictionary *) responseObject[#"d"];
NSLog(#"RESULT: %#", result);
NSLog(#"personm: %#", person);
//We are executing the block as soon as we have the results.
if (success) {
success(responseObject);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [error description]);
//result = [error];
}
];
}
Edit:
[self webServiceResponceForURL:#"foo://foo" dictionary:nil success:^(NSDictionary *responseObject) {
//your code here
}
[self webServiceResponceForURL:#"foo://foo" dictionary:nil success:^(NSDictionary *responseObject) {
//your code here
}
Here you will got complete responseObject in form NSDictionary. You can assign responseObject to instance variable. Now This instance Variable will be used at point time. in your case, it will passed on button event.

Resources