How to implement the Oauth in iPad application?
How does AFOAuth2Client manages refreshing token mechanism in oauth 2.0?
Is there any method to implement it inside the class or do we have to implement it in our own way? How to check the token is expired or not?
The way that I have solved this is to wrap all my requests with a code block which will refresh the access token if needed e.g.
Add some typedefs for success and failure blocks:
typedef void (^YFRailsSaasApiClientSuccess)(AFJSONRequestOperation *operation, id responseObject);
typedef void (^YFRailsSaasApiClientFailure)(AFJSONRequestOperation *operation, NSError *error);
Then the request method is:
- (void)getProductsWithSuccess:(YFRailsSaasApiClientSuccess)success failure:(YFRailsSaasApiClientFailure)failure {
NSLog(#"getProductsWithSuccess");
success = ^(AFJSONRequestOperation *operation, id responseObject) {
[self getPath:#"api/1/products"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"getProductsWithSuccess: success");
// TODO: handle response
if (success) {
success((AFJSONRequestOperation *)operation, responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"getProductsWithSuccess: failure");
if (failure) {
failure((AFJSONRequestOperation *)operation, error);
}
}];
};
[self refreshAccessTokenWithSuccess:success failure:failure];
}
And the method which checks for token expiry and refreshes it if needed is:
- (void)refreshAccessTokenWithSuccess:(YFRailsSaasApiClientSuccess)success failure:(YFRailsSaasApiClientFailure)failure {
NSLog(#"refreshAccessTokenWithSuccess");
if (self.credential == nil) {
if (failure) {
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:#"Failed to get credentials" forKey:NSLocalizedDescriptionKey];
NSError *error = [NSError errorWithDomain:#"world" code:200 userInfo:errorDetail];
failure(nil, error);
}
return;
}
if (!self.credential.isExpired) {
NSLog(#"refreshAccessTokenWithSuccess: credential has not expired");
if (success) {
success(nil, nil);
}
return;
}
NSLog(#"refreshAccessTokenWithSuccess: refreshing credential");
[self authenticateUsingOAuthWithPath:#"oauth/token"
refreshToken:self.credential.refreshToken
success:^(AFOAuthCredential *newCredential) {
NSLog(#"Successfully refreshed OAuth credentials %#", newCredential.accessToken);
self.credential = newCredential;
[AFOAuthCredential storeCredential:newCredential
withIdentifier:self.serviceProviderIdentifier];
if (success) {
success(nil, nil);
}
}
failure:^(NSError *error) {
NSLog(#"An error occurred refreshing credential: %#", error);
if (failure) {
failure(nil, error);
}
}];
}
Full source code is up on GitHub: https://github.com/yellowfeather/rails-saas-ios.
Related
I have a framework and a project. My framework is responsible for web services.
From Project user insert username and password. Then it passes these parameters by calling sendLogin method inside the framework.
Inside framework it takes a while to check and validate username and password. If username and password are correct it will get a token number from server.
Until here everything works fine. But I want to know how to send this token back to main program?
I tried completion method but I failed. Here is definition:
Project:
- (IBAction)bankLoginPressed:(id)sender
{
[registerUser sendLogin:^(NSInteger *accessCode){
NSLog(#"access code == %tu ",accessCode);
}];
}
Inside framework
typedef void (^HttpCompletionBlock) (NSInteger *);
-(void) sendLogin :(HttpCompletionBlock)completionHandler
{
NSString *string = #"https://myserver/customer_authentication";
NSDictionary *parameters = #{#"member_id": #"1234", #"access_code": #"password", #"device_id":#"874627864"};
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:string parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"JSON: %#", responseObject);
if (responseObject[#"secret_token"])
{
NSLog(#"Secret is= %#",responseObject[#"secret_token"]);
//Here I needd to send back token number????
}
}
failure:^(NSURLSessionTask *operation, NSError *error)
{
NSLog(#"Error: %#", error);
}];
}
typedef void (^HttpCompletionBlock) (NSString *token, NSError *error);
-(void) sendLogin :(HttpCompletionBlock)completionHandler
{
NSString *string = #"https://myserver/customer_authentication";
NSDictionary *parameters = #{#"member_id": #"1234", #"access_code": #"password", #"device_id":#"874627864"};
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:string parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"JSON: %#", responseObject);
if (responseObject[#"secret_token"])
{
NSLog(#"Secret is= %#",responseObject[#"secret_token"]);
//Here I needd to send back token number????
return completionHandler(responseObject[#"secret_token"],nil);
}
}
failure:^(NSURLSessionTask *operation, NSError *error)
{
NSLog(#"Error: %#", error);
return completionHandler(nil,error);
}];
}
- (IBAction)bankLoginPressed:(id)sender
{
[registerUser sendLogin:^(NSString *token, NSError *error){
if(error == nil)
{
NSLog(#"access code == %# ",token);
}
else
{
NSLog(#"Error == %# ",error);
}
}];
}
Hii i'm trying to get linkedin connection. i have see some same questions but did not find any relevant solution. please help me how can i find connection using latest SDK and which permission i need for connections.
i have used argument as
#define LinkedInApiUrl #"http://api.linkedin.com/v1/people/~/connections:(id,headline,first-name,last-name)"
- (void)requestMeWithToken:(NSString *)accessToken
{
[self.client GET:[NSString stringWithFormat:#"%#?oauth2_access_token=%#&format=json",LinkedInApiUrl,accessToken] parameters:nil success:^(AFHTTPRequestOperation *operation, NSDictionary *result) {
NSLog(#"current user %#", result);
[self linkedinAuthenticationResponse:result error:nil];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"failed to fetch current user %#", error);
[self linkedinAuthenticationResponse:nil error:error];
}];
}
- (LIALinkedInHttpClient *)client
{
LIALinkedInApplication *application = [LIALinkedInApplication
applicationWithRedirectURL:[NSString stringWithFormat:#"%#",#"https://www.something.com"]
clientId:LINKEDIN_CLIENT_ID
clientSecret:LINKEDIN_CLIENT_SECRET
state:#"someState"
grantedAccess:#[ LISDK_EMAILADDRESS_PERMISSION, LISDK_BASIC_PROFILE_PERMISSION,LISDK_RW_COMPANY_ADMIN_PERMISSION,LISDK_W_SHARE_PERMISSION ]];
return [LIALinkedInHttpClient clientForApplication:application presentingViewController:self];
}
I'm getting NSURLErrorDomain error -1012. error in xcode 6.2 , I'm able to get the access token successfully, I tried reseting simulator.. here is the code
-(void)getLinkedInData:(LIALinkedInHttpClient*)client
{
[client getAuthorizationCode:^(NSString *code) {
[client getAccessToken:code success:^(NSDictionary *accessTokenData) {
NSLog(#"Access token data: %#",accessTokenData);
NSString *accessToken = [accessTokenData objectForKey:#"access_token"];
NSLog(#"Access token: %#",accessToken);
[client GET:[NSString stringWithFormat:#"https://api.linkedin.com/v1/people/~?oauth2_access_token=%#&format=json", accessToken] parameters:nil success:^(AFHTTPRequestOperation *operation, NSDictionary *result) {
NSLog(#"current user %#", result);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"failed to fetch current user %#", error.localizedDescription);
}];
} failure:^(NSError *error) {
NSLog(#"Querying accessToken failed %#", error);
}];
} cancel:^{
NSLog(#"Authorization was cancelled by user");
} failure:^(NSError *error) {
NSLog(#"Authorization failed ::::%#", error.localizedDescription);
}];
}
I'm not sure if this'll answer your question, but I've found that when a calling a web service synchronously, which returns a 401 "Not authorised" error, iOS will simply return a vague -1012 error.
iOS: How can i receive HTTP 401 instead of -1012 NSURLErrorUserCancelledAuthentication
So, try to check whether you're having authentication problems, and tackle the problem from there.
I have three API's I pull data from, and put into a UITableView inside of my ViewController.m.
Is there a way to still let the UITableView load if one of the websites isn't loading?
Right now, the ViewController.m just doesn't load if all 3 sources aren't loading per my method.
Here's the method I use:
- (void)loadOneWithSuccess:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success
failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *tNE = [defaults objectForKey:[NSString stringWithFormat:#"tNE%#", bn]];
NSString *path = [NSString stringWithFormat:#"xx/%#/", tNE];
[self.eObjectManager getObjectsAtPath:path parameters:nil success:success failure:failure];
}
- (void)loadMedia {
self.combinedModel = [NSMutableArray array];
// Here's the #1
[self loadOneWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
// Here's the trick. call API2 here. Doing so will serialize these two requests
[self loadTwoWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
// Here's the trick. call API3 here. Doing so will serialize these two requests
[self loadThreeWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
[self sortCombinedModel];
[self.tableView reloadData];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
}];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
}];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
}];
}
So if API1 doesn't load, API2 and API3 will still load and show in the UITableView in ViewController.m.
Maybe you can try something like this, first define tree bool variables: finish1, finish2 and finish3
- (void)loadMedia {
self.combinedModel = [NSMutableArray array];
[self loadOneWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
finish1 = true;
[self reloadTableData]
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
finish1 = true;
[self reloadTableData]
}];
[self loadTwoWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
finish2 = true;
[self reloadTableData]
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
finish2 = true;
[self reloadTableData]
}];
[self loadThreeWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
finish2 = true;
[self reloadTableData]
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
finish3 = true;
[self reloadTableData]
}];
}
- (void) reloadTableData {
if (finish1 && finish2 && finish3) {
[self sortCombinedModel];
[self.tableView reloadData];
}
}
The loadOne, loadTwo ... functions have a disadvantage which is that they take two block parameters, one for success and one for fail. If you change those to take a single block that handles success or failure, it will be much easier to carry on after errors occur.
EDIT Change how you call your eObjectManager by not directly passing on the completion and failure blocks. Instead, implement those blocks and rearrange the params to match the single block interface...
- (void)betterLoadOneWithCompletion:(void (^)(RKObjectRequestOperation*, RKMappingResult*, NSError *))completion {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *tNE = [defaults objectForKey:[NSString stringWithFormat:#"tNE%#", bn]];
NSString *path = [NSString stringWithFormat:#"xx/%#/", tNE];
[self.eObjectManager getObjectsAtPath:path parameters:nil success:^(RKObjectRequestOperation *op, RKMappingResult *map) {
// success! pass the operation, map result and no error
completion(op, map, nil);
} failure:^(RKObjectRequestOperation *op, NSError *error) {
// fail. pass the operation, no result and the error
completion(op, nil, error);
}];
}
It can still call your old function or some external library with two blocks, but it combines the result into a single block. The caller of this expects that they will either get a good RKMappingResult and a nil NSError, or a nil for the result parameter and an instance of an error. With this api, we can easily fix your method to just log errors as they occur and carry on, error or not...
- (void)loadMedia {
self.combinedModel = [NSMutableArray array];
// changed the loadOneWithCompletion signature to take just a single block, calling it on success or fail
[self betterLoadOneWithCompletion:^(RKObjectRequestOperation *op, RKMappingResult *mappingResult, NSError *error) {
// if it worked, handle the results
if (!error) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
} else {
// if it didn't work, log the error, but execution continues
NSLog(#"No?: %#", error);
}
// even if it didn't work, we can keep going...
[self betterLoadOneWithCompletion:^(RKObjectRequestOperation *op, RKMappingResult *mappingResult, NSError *error) {
// same - handle results
if (!error) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
} else {
// same - log the error if there is one
NSLog(#"No?: %#", error);
}
// same - log the error and keep going
[self betterLoadOneWithCompletion:^(RKObjectRequestOperation *op, RKMappingResult *mappingResult, NSError *error) {
// same...
if (!error) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
} else {
NSLog(#"No?: %#", error);
}
[self sortCombinedModel];
[self.tableView reloadData];
}];
}];
}];
}
Beginner ios AFNetworking 2.0 Qns: Having subclassed AFHTTPSessionManager to something like "MyAPIManager" and placed my all my API calls (GET/POST/PUT etc.) in this custom manager class, I'm having problems making use of the response on request success in another class (say class B).
I know I can refactor this and pluck out the POST call portion to class B, so that I can dump the relevant class B methods in the callback, but this would get messy, especially with multiple API calls.
I want to pass this response (e.g. the returned objectId) to another class and right now I'm just using a NSNotification which class B listens for, but this still feels a bit 'hackish' and am wondering if there is a better way to do this.
Currently in MyAPIManager : AFHTTPSessionManager:
- (void) POSTRecordJson:(NSDictionary *)json
{
[self POST:#"classes/Record/" parameters:json success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Posted JSON: %#", json.description);
if ([responseObject isKindOfClass:[NSDictionary class]]) {
NSLog(#"Response: %#", responseObject);
//Notify objectId received
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ReceivedObjectIdNotification"
object:self
userInfo:responseObject];
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
And in Class B I've called:
MyApiManager *manager = [MyApiManager sharedInstance];
[manager POSTRecordJson:someJSONdict];
you could do 2 things.. by using a protocol/delegate or a block..
but i, personally, prefers block soo..
first make a block Datatype
typedef void(^SuccessBlock)(id success); example
and add the parameter with the block on it
- (void) POSTRecordJson:(NSDictionary *)json success:(SuccessBlock)success
{
[self POST:#"classes/Record/" parameters:json success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Posted JSON: %#", json.description);
if ([responseObject isKindOfClass:[NSDictionary class]]) {
NSLog(#"Response: %#", responseObject);
//Notify objectId received
success(responseObject);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
and to call the new function..
MyApiManager *manager = [MyApiManager sharedInstance];
[manager POSTRecordJson:someJSONdict success:^(id result){
NSDictionary *dictionary = (NSDictionary *)result;
NSLog(#"response: %#",dictionary)
}];
You would want to pass a completion block into your -POSTRecordJson: method.
For example, you would refactor your method to do the following:
- (void) POSTRecordJson:(NSDictionary *)json completion:(void(^)(BOOL success, id response, NSError *error))completion
{
[self POST:#"classes/Record/" parameters:json success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Posted JSON: %#", json.description);
if ([responseObject isKindOfClass:[NSDictionary class]])
{
NSLog(#"Response: %#", responseObject);
if (completion) //if completion is NULL, calling it will crash your app so we always check that it is present.
{
completion(YES, responseObject, nil);
}
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#", error);
if (completion)
{
completion(NO, nil, error);
}
}];
}
You could then handle this implementation like so:
//assuming `manager` and `dictionary` exist.
[manager POSTRecordJson:dictionary completion^(BOOL success, id response, NSError *error) {
if (success)
{
//do something with `response`
}
else
{
//do something with `error`
}
}];
However, if you are a beginner with AFNetworking and you want to adopt a great structure for handling web services, you should check out this excellent blog post.
You can use blocks to send the response back to the class after the response received from the server:
- (void) POSTRecordJson:(NSDictionary *)json response:(void (^)(id response, NSError *error))responseBlock
{
[self POST:#"classes/Record/" parameters:json success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Posted JSON: %#", json.description);
if ([responseObject isKindOfClass:[NSDictionary class]]) {
NSLog(#"Response: %#", responseObject);
responseBlock(responseObject, nil);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#", error);
responseBlock(nil, error);
}];
}