RestKit: How to resubmit failed request after re-authenticating? - ios

The API I'm developing against requires me to present an authentication token in a custom HTTP header. This token expires every few minutes, and could happen while the user is still within the app as long as they have been idle long enough. When the token has expired I receive a 403 response but I only find out after attempting a request.
What's the best way to get RestKit to automatically reauthenticate and retry the request so I don't have to put in this logic everywhere I make a request? Responses to similar questions have suggested using the RKRequestDelegate protocol or the RKObjectLoaderDelegate protocol but unfortunately these are no longer part of RestKit (as of 0.20).
Any idea what the "correct" approach should be now? Should I be subclassing RKObjectManager and tacking on a retry to each of the request operations or should I provide a custom HTTPOperation or HTTPClient class or is there some better approach altogether? Thanks!

Catch it in Failure block , and check for the status code and re-do the authentication
RKObjectRequestOperation *requestOp = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[getObjResp]];
[requestOp setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
....
}
} failure:^(RKObjectRequestOperation *operation, NSError *error){
// Here your status code check
// Here your retry-code
}

Related

AFNetworking API call status code

Is there a possibility that AFNetworking takes API status code in consideration as well?
Call returns 200 HTTP code that it is ok, but status code is 406 which indicates failure.
This results to AFNetworking success block being called, even though API call failed.
Is there a solution to this?
Code:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString *APICallURL = [NSString stringWithFormat:#"%#/%#", SERVER_URL, #"something.php"];
[manager GET:APICallURL parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (success) {
success();
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (failure) {
failure(error);
}
}];
X-API-Status-Code is a totally non-standard header (as any header starting with X-... actually, by definition).
That header is a proprietary way that your specific server chose to report the API status code. It could have picked any other proprietary header to do so, maybe calling it X-API-ReturnCodeor whatnot.
So I doubt that AFNetworking will ever support this natively.
But you can probably create your own AFURLResponseSerialization subclass and implement validateResponse, so that your code check for this proprietary header and act accordingly.
It will not reach failure block for any failure status code from server. With respect to AFNetworking, network call is successful with a error code from server.
It will reach failure block only when we are not able to successfully finish the network call, like
1) Time out error
2) Server down and not able to reach
3) Network loss etc
Solution is we should check the status code of network call in success block and identify error responses from server

AFNetworking 2.0 - unexpected NSURLErrorDomain error -1012

We ran into the following issue with our app that uses AFNetworking 2.0.
When using AFHTTPRequestOperationManager's GET method, we got an error NSURLErrorDomain code -1012. The request used HTTPS and the server does not require user authentication. The request never reached the server by the way.
We have run several tests and this is the first time the error was produced and we are wondering how this error can get produced because it does not seem relevant.
Setup of AFHTTPRequestOperationManager :
httpOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:
[NSURL URLWithString: HTTPS_URL)]];
httpOperationManager.responseSerializer =
[AFXMLParserResponseSerializer serializer];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled: YES];
GET REQUEST
AFHTTPRequestOperation *op =[httpOperationManager GET:
[NSString stringWithFormat:SOME_PATH]
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//code to setup NSXMLParser ...
}
failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error %#", [error localizedDescription]);
}];
I think you already solved the problem, but if you are trying to authenticate in a server that doesn't have a valid certificate you have to set YES for property allowInvalidCertificates in your AFHTTPRequestOperationManager object:
[yourManager.requestSerializer setAuthorizationHeaderFieldWithUsername:#"your_username" password:#"your_password"];
[yourManager.securityPolicy setAllowInvalidCertificates:YES];
Also, as #a1phanumeric said, it can be necessary to include this line:
[yourManager.securityPolicy setValidatesDomainName:NO];
Cheers.
NSURLErrorDomain -1012 is NSURLErrorUserCancelledAuthentication. (See the error code list and search for -1012.)
You state, "the server does not require user authentication". But this error would not be called if that were true.
Possible causes:
Your server is erroneously requesting authorization (a server bug)
The URL formed with HTTPS_URL and SOME_PATH is not what you expect, and some other server is requesting authorization
Some intermediary (like a proxy server, or an access point) is requiring authorization.
Some debugging tips:
Set breakpoints inside the AFNetworking implementation to see which URL is being hit
Configure AFHTTPRequestOperationLogger so you can see the actual request body and response in your console log
Make the same request with curl or Advanced Rest Client and observe the server's response
Side note: I think [NSString stringWithFormat:SOME_PATH] is pointless - why not just use SOME_PATH?

NSURLConnection - Disable the authentication challenge response mechanism

SITUATION
Using AFNetworking (NSURLConnection) to access my server API
The API needs Basic Authentication with token as username
The API returns HTTP 401 when token is invalid
I set the Basic Authentication headers before any request
If it returns 401, I retry like this:
AFHTTPRequestOperation *requestOperation = [MyHTTPClient.sharedClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
processSuccessBlock(operation, responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
processFailureBlock(operation, error);
if (operation.response.statusCode == 401)
[MyHTTPClient.sharedClient refreshTokenAndRetryOperation:operation success:processSuccessBlock failure:processFailureBlock];
}];
PROBLEM
When the server returns HTTP 401 with a Basic Authentication challenge, AFNetworking/NSURLConnection sends the identical request twice (initial request and then in answer to the authentication challenge)
But because of the code above that I handle the HTTP 401 myself, this is totally unnecessary and I want it to stop answering the authentication challenge automatically
WHAT I'VE TRIED
Responding with cancelAuthenticationChallenge: to willSendRequestForAuthenticationChallenge: is aborting the second call, but it gives the error code -1012 (NSURLErrorUserCancelledAuthentication) instead of 401 and masks the real response
How do you disable the authentication challenge response mechanism so you get the servers response without calling it twice?
You have a few of options to fully customize the authentication handling:
Utilize setWillSendRequestForAuthenticationChallengeBlock: and setAuthenticationAgainstProtectionSpaceBlock: from class AFURLConnectionOperation and set corresponding blocks where you can tailor the mechanism you require.
The headers contain documentation.
Override connection:willSendRequestForAuthenticationChallenge: in a subclass of AFURLConnectionOperation. This will effectively override the complete authentication mechanism setup in AFNetworking.
Note, that you cannot disable the "server validates client identity" authentication challenge -- this is part of the server. You MUST provide the correct credentials for the requested authentication method.
Try this.
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
SecTrustRef trust = challenge.protectionSpace.serverTrust;
NSURLCredential *cred;
cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
}

RestKit requests hang on HTTP errors

I am trying to make my app handle HTTP client errors. Ideally, I would like to run a static method whenever such an error occurs, regardless of the request that caused it.
It seems that I am doing something wrong -- I have tried to add a RKDynamicResponseDescriptor to call the method, but it doesn't get called. Whenever my server responds with an error, the request never finishes (the network activity indicator keeps spinning and the AFNetworkingOperationDidFinishNotification is never posted.)
I can reproduce the behavior by creating a new AFHTTPRequest request like this:
request = [[RKObjectManager sharedManager].HTTPClient requestWithMethod:#"GET" path:#"events" parameters:nil];
AFHTTPRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"%#", JSON);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
Neither the success nor the failure block is called. If I replace the first line with
request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
the blocks get called. How can I make RestKit handle the HTTP error response?
Edit 1: Apparently this happens in both cases when the server returns 401 with an authentication challenge. I have tried setting a breakpoint in RKHTTPRequestOperation in didReceiveAuthenticationChallenge, but it is not triggered. My Fiddler shows that only one request is sent.
After lots of fiddling, I think I have found the reason for this problem. The AFUrlConnectionRequest class implements the connection:willSendRequestForAuthenticationChallenge:delegate method when SSL pinning is enabled. The implementation simply returns if the authentication challenge is not related to SSL.
From the documentation of NSUrlConnectionDelegate:
This method allows the delegate to make an informed decision about connection authentication at once. If the delegate implements this method, it has no need to implement
connection:canAuthenticateAgainstProtectionSpace:,
connection:didReceiveAuthenticationChallenge:,
connectionShouldUseCredentialStorage:.
In fact, these other methods are not invoked.
The methods will not be invoked, and thus RestKit never knows that the request resulted in HTTP error 401.
I solved the problem by removing this define, so the delegate method is not implemented:
#define _AFNETWORKING_PIN_SSL_CERTIFICATES_

User Login With AFNetworking

I am building my first iOS app.
I have got the backend code done, but I am struggling with the Objective-C part of it.
I have a signup / login page.
But I don't know how to send that data to my server using Objective C.
I have read that AFNetworking is good, but I was wondering how I could use that for user login .
I have downloaded and added AFNetworking to my XCode Project and set up headers.
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://examplewebsite.com]];
[client setDefaultHeader:#"key" value:#"value"];
[client setAuthorizationHeaderWithUsername:#"username" password:#"password"];
[client setAuthorizationHeaderWithToken:#"token"];
NSURLRequest *request = [client requestWithMethod:#"someMethod" path:#"somePath" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
but I am still lost.
Since you're trying to login to your own API, you don't want setAuthorization stuff. That's for basic HTTP auth. Instead you want to use getPath:parameters:success:failure or the postPath version, depending on if your backend is expecting HTTP GET or HTTP POST.
Pass your userid / password in the parameters argument. You should set parameterEncoding to be the correct format. You're probably using HTTP Forms url encoding, or JSON. Whatever your backend expects.
You don't want to set the authorization headers in this case, since this is for "basic access HTTP authentication", which is a method for a HTTP user agent to provide a user name and password when making a request to a server.
You want to use your own API and interact with a restful server and therefore, I would recommend, that you subclass AFHTTPClient -> interact with an API, Web Service, or Application. - Take a look at the examples in the AFNetworking zip archive, if you have difficulties in subclassing AFHTTPClient.
Since you want to create an app with user login, the app needs to send these information to your server, and the server should return if the login was succesful or not.
This can be done like so - HTTP POST.
- (void)login {
// Login information from UITextFields
id params = #{
#"username": self.usernameField.text,
#"password": self.passwordField.text
};
//Call the AFHTTP subclass client, with post block. postPath = the path of the url, where the parameters should be posted.
[[theAuthAPIClient sharedClient] postPath:#"/login"
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//handle succesful response from server.
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// handle error - login failed
}
}];
}
You need to pass the parameters in the right format, depending on what format your server expects. This can be done by setting the right encoding in your AFHTTPClient subclass -> ParameterEncoding
Since I came here while searching for a working solution for AFNetworking 2.0, not knowing that AFHTTPClient was removed from the Framework, I will post the new way to establish this connection here:
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:#"http://examplewebsite.com"]];
[manager setRequestSerializer:[AFHTTPRequestSerializer serializer]];
[manager.requestSerializer setAuthorizationHeaderFieldWithUsername:#"userName" password:#"password"];

Resources