How to handle OAuth2 refreshToken with RestKit 0.20 - oauth-2.0

I know for method
-[[RKObjectManager sharedManager].HTTPClient setAuthorizationHeaderWithToken:]
and I suppose that this method accepts accessToken. How to handle OAuth2 refreshToken?

Ok, I resolve my problem.
First of all, you have to download AFOAuth2Client from http://goo.gl/zdTdlJ
This is AFHTTPClient extension witch handles OAuth2 authentication and here's snippet how to initialize it and set it into RestKit as HTTPClient.
AFOAuthCredential* credential = [AFOAuthCredential credentialWithOAuthToken:accessToken tokenType:kAFOAuthCodeGrantType];
[credential setRefreshToken:refreshToken expiration:expirationDate];
AFOAuth2Client* oAuth2Client = [AFOAuth2Client clientWithBaseURL:[NSURL URLWithString:baseUrl]
clientID:clientID
secret:secret];
[oAuth2Client setAuthorizationHeaderWithToken:accessToken];
// This line is for environment where you don't have valid certificate.
// For example for development environment
[oAuth2Client setAllowsInvalidSSLCertificate:YES];
[[RKObjectManager sharedManager] setHTTPClient:oAuth2Client];
[[RKObjectManager sharedManager] getObjectsAtPath:path parameters:nil
success:^(
RKObjectRequestOperation* operation, RKMappingResult* mappingResult) {
// Handle success response
} failure:^(RKObjectRequestOperation* operation, NSError* error) {
// Handle failure response
}];

Related

AFHTTPRequestOperation dependency

I have the following scenario in an application that uses AFNetworking to make services calls:
I call a special service that will generate a token for me
I call the service that I want, sending this token as a parameter
I call another special service to destroy the token.
I have to follow these 3 steps every time I make a request to the server. I cannot change the way the server works, so I have to comply to this requirement. I also cannot use the same token for more than one request.
My question is the following - I tried to accomplish this using AFHTTPRequestOperations:
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.manager.requestSerializer requestWithMethod:#"POST" URLString:[[NSURL URLWithString:#"serviceName.json" relativeToURL:self.manager.baseURL] absoluteString] parameters:#{ #"token": token } error:&serializationError];
AFHTTPRequestOperation *myRequestOperation = [self.manager HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
// Success login
} failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
// Failure logic
}];
[myRequestOperation addDependency:createTokenRequestOperation];
where self.manager is an instance of AFHTTPRequestOperationManager, but there is a problem - I do not have a value for token.
Since myRequestOperation should execute only after point 1 from the list above, I make it dependent on the operation that will get me a token.
Now comes my confusion - how can I create an operation that uses a parameter from a previous operation, when I need to have both of them instantiated in order to make the one depend on the other?
Since I was not able to find a solution that will work for me, I ended up using PromiseKit, which allows me to chain asynchronous calls like this:
[NSURLConnection promise:rq1].then(^(id data1){
return [NSURLConnection promise:rq2];
}).then(^(id data2){
return [NSURLConnection promise:rq3];
}).then(^(id data3){
// Work with the data returned from rq3
});

How to automatically refresh expired token with AFOAuth2Manager?

I'm writing a small iOS client for a server protected with OAuth2.
I'm wondering if is it possible using AFOAuth2Manager [here] auto-refreshing the expired token.
The idea is that the logic for refreshing the client when the server responds with a 401, or raise an error when the refresh method returns a 401 should be quite common, so probably it is integrated in some library.
I created a subclass of AFOAuth2Manager
In this subclass I override this method:
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
return [self HTTPRequestOperationWithRequest:request
success:success
failure:failure
checkIfTokenIsExpired:YES];
}
calling a custom method with an additional parameter: checkIfTokenIsExpired. This is required in order to avoid infinite loops.
The implementation of this method is straigth forward: if we don't need to check the token just call the super class.
if (!checkIfTokenIsExpired) {
return [super HTTPRequestOperationWithRequest:request
success:success
failure:failure];
}
otherwise we perform the request with a custom failure block
else {
return [super HTTPRequestOperationWithRequest:request
success:success
failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
if (operation.response.statusCode == ERROR_CODE_UNAUTHORIZED) { //1
[self reauthorizeWithSuccess: ^{ //2
NSURLRequest *req = [self.requestSerializer requestByAddingHeadersToRequest:request]; //3
AFHTTPRequestOperation *moperation = [self HTTPRequestOperationWithRequest:req //4
success:success
failure:failure
checkIfTokenIsExpired:NO];
[self.operationQueue addOperation:moperation]; //5
} failure: ^(NSError *error) {
failure(nil, error);
}];
}
else {
failure(operation, error); //6
}
}];
}
//1: check the http status code, if 401 try to automatically re-authorize.
//2: reauthorize is a private mathod that uses AFOAuthManager to refresh the token.
//3: In this case we are re-authorized with success and we want to resubmit a copy of the previous request. The method requestByAddingHeadersToRequest: just copy all the header fields from the previous request.
//4: Create a copy of the previous request, but this time the last parameter is false because we don't want check again! The successBlock and failureBlock are the same of the previous request.
//5: Add the operation to the queue.
//6: If the reauthorize method fails just call the failure block.
Unfortunately I didn't found any framework for solve this problem so I wrote a short wrapper around AFNetworking (if someone is interested I can publish on github)
The logic is to execute the request, and in case of http response 401, try to refresh the auth-token and when it's done to re-execute the previous request.
I was searching an answer for this problem and "Matt", the creator of AFNetworking, suggest this:
the best solution I've found for dealing with this is to use dependent
NSOperations to check for a valid, un-expired token before any
outgoing request is allowed to go through. At that point, it's up to
the developer to determine the best course of action for refreshing
the token, or acquiring a new one in the first place.
Simple, but effective?, trying now, will edit with report...
Swift solution with Alamofire 4.0. Based on RequestAdapter and RequestRetrier protocols: example link

AFNetworking to REST API returns 401

I'm trying to use a REST API in my iOS app. I know it works because I can make the login request once. Every subsequent request fails with a 401 error. Even if I delete the app from the simulator it still can't be called again until I change the simulator type to one that I haven't used before (i.e. iPad 2, iPhone6, etc.). I can also use a service like https://www.hurl.it to make the same request with the same parameters as many times as I'd like. I'm using AFNetworking and AFHTTPRequestOperationManager. What am I doing wrong?
self.manager = [[AFHTTPRequestOperationManager alloc]initWithBaseURL:[NSURL URLWithString:#"https://api.mydomain.com/"]];
[self.manager POST:#"services/json/user/login" parameters:#{#"username":#"USERNAME", #"password":#"PASSWORD"} success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (![responseObject isKindOfClass:[NSDictionary class]]) { return; }
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSDictionary* json = responseObject;
self.sessionName = json[#"session_name"];
self.sessionId = json[#"sessionid"];
[defaults setObject:self.sessionName forKey:#"SessionNameKey"];
[defaults setObject:self.sessionId forKey:#"SessionIDKey"];
if (completion) { completion(); }
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Session request failed: %#", error.userInfo);
}];
If first login called success, you should get some access_token which you use to send along with any subsequent calls.
If API has basic authentication, then you need to pass credentials in HTTP header.
To set credentials in header you can use following methods:
[self.manager.requestSerializer setAuthorizationHeaderFieldWithUsername:#"username" password:#"password"];
(Unauthorised) 401 means you don't have access to the service. Make sure you are trying with correct credentials. Apparently there's nothing wrong with the code.
Update for iOS9 with Swift 2.0 using Bearer authorization
I had the same problem, actually the sessionManager.session.configuration.HTTPAdditionalHeaders get overwritten by the requestSerializer on iOS9, which cause the 401 Access denied error.
Code that was working on iOS8, but not on iOS9:
sm.session.configuration.HTTPAdditionalHeaders = ["Authorization" : "Bearer \(token!)"]
New code for iOS9
sm.requestSerializer.setValue("Bearer \(token!)", forHTTPHeaderField: "Authorization")

AFNetworking - Twitter OAuth Redirect URL

I'm a web developer new to iOS development and I'm trying to tie over the client/server Twitter OAuth process over to an iPhone app.
I want use a passport Twitter strategy on a node.js server for my authentication. So in a web client setting I make a request to a twitter authorization URL I've set up on the server, and the server responds with a redirect that takes me to the Twitter Login page w/ the access token provided. This redirect URL is automatically handled by the passport Twitter strategy.
How do I create the same web redirect effect within the context of an app using AFNetworking. I know how to make a basic POST and GET requests, but is there some setting where AFNetworking can handle the server response by opening safari and redirecting to the link provided?
The code I provided below doesn't throw any errors, but I doesn't redirect...
iOS Code:
- (IBAction)twitterLogin:(id)sender {
NSLog(#"Twitter Login");
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:#"http://54.148.118.153/auth/twitterX/" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
Node.js server code:
var TwitterStrategy = require('passport-twitter').Strategy;
// twitter authentication and login
app.get('/auth/twitterX', passport.authenticate('twitter'));
// handle callback after twitter has authenticated user
app.get('/auth/twitterX/callback',
passport.authenticate('twitter',{
successRedirect: '/',
failureRedirect: '/'
}
));
// used to serialize user
passport.serializeUser(function(user,done){
done(null, user.twitter_id);
});
// used to deserialize the user
passport.deserializeUser(function(twitter_id,done){
connection.query('SELECT * from User_Twitter where id = '+twitter_id, function(err,rows){
done(err,rows[0]);
});
});
// passport Twitter protocol
passport.use(new TwitterStrategy({
consumerKey:'G17pGaKQUYPPsy5o5H7siZvWj',
consumerSecret:'qiTVnzHsIhWDqlkPd4vF2Xao7L9wvAh08YGpwOpXb5CowesqIb',
callbackURL:'http://54.148.118.153/auth/twitterX/callback'
},
function(token, tokenSecret, profile,done){
//Make code asynchronous - MySQL query won't fire until we have all data back from twitter
process.nextTick(function(){
//Do stuff with Twitter data
})
}
Let me introduce you to AFOAuth2Client, maintained by the same folks who maintain AFNetworking: https://github.com/AFNetworking/AFOAuth2Client
I am going to paste their example code documentation here:
NSURL *url = [NSURL URLWithString:#"http://example.com/"];
AFOAuth2Client *oauthClient = [AFOAuth2Client clientWithBaseURL:url clientID:kClientID secret:kClientSecret];
[oauthClient authenticateUsingOAuthWithPath:#"/oauth/token"
username:#"username"
password:#"password"
scope:#"email"
success:^(AFOAuthCredential *credential) {
NSLog(#"I have a token! %#", credential.accessToken);
[AFOAuthCredential storeCredential:credential withIdentifier:oauthClient.serviceProviderIdentifier];
}
failure:^(NSError *error) {
NSLog(#"Error: %#", error);
}];
Check out that NSLog statement in that success block: once you get that token, you can replace that NSLog statement with whatever storage solution you want to keep the token on the client.
Also, if you have not checked it out yet, I would highly recommend you use CocoaPods as your go-to dependency manager. That way, with one line you can bring in a dependency like AFOAuth2Client: http://cocoapods.org

Make a synchronous HTTP call with RestKit

When logging a user into my application I need to pull a user object down from the server using only the username. This returns the userId (among other things) that I need in order to make other API calls. From that point I'll make a couple other HTTP calls using the userId. How can I make a synchronous call to completely pull down the user object before sending the other calls?
I've setup my object mapping in my app delegate class, which works perfectly, and am using this code to pull the user object down from the server:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/users/" stringByAppendingString:[_userNameField text]] delegate:self];
This is what I've tried... as suggested here: Making synchronous calls with RestKit
RKObjectLoader* loader = [[RKObjectManager sharedManager] objectLoaderForObject:currentUser method:RKRequestMethodPUT delegate:nil];
RKResponse* response = [loader sendSynchronously];
However this code (1) uses the deprecated method objectLoaderForObject and (2) crashes saying 'Unable to find a routable path for object of type '(null)' for HTTP Method 'POST''.
Putting aside the question of whether this is the ideal design for an iPhone application, I was able to accomplish what I was hoping using blocks.
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/users/" stringByAppendingString:[_userNameField text]] usingBlock:^(RKObjectLoader* loader) {
loader.onDidLoadResponse = ^(RKResponse *response) {
NSLog(#"Response: \n%#", [response bodyAsString]);
};
loader.onDidLoadObjects = ^(NSArray *objects) {
APIUser *apiUser = [objects objectAtIndex:0];
NSLog(#"user_id is %i", apiUser.user_id);
};
loader.onDidFailWithError = ^(NSError *error) {
UIAlertView *badLoginAlert = [[UIAlertView alloc]initWithTitle:NSLocalizedString(#"LOGIN_FAILED", nil)
message:NSLocalizedString(#"BAD PASSWORD OR USERNAME", nil)
delegate:self
cancelButtonTitle:NSLocalizedString(#"OK", nil)
otherButtonTitles:nil];
[badLoginAlert show];
};
}];
Hope this helps someone.

Resources