I'm communicating with a server that validates a password and returns a 401 error on invalid password, together with a json body specifying the number of failed attempts. That number is incremented by the server on each failed validation.
The problem I'm facing is that when NSURLConnection gets a 401 response, it kicks an authentication mechanism that involves these delegate methods:
connection:canAuthenticateAgainstProtectionSpace:
connection:didReceiveAuthenticationChallenge:
If I return NO in the canAuthenticate method, a new identical request will be made. This will result in the server incrementing the failed attempts a second time (which is obviously not desired) and I'll get a 401 response (connection:didReceiveResponse:)
If I return YES in the canAuthenticate method, then the didReceiveAuthenticationChallenge method is called. If I want to stop the second request, I can call [challenge.sender cancelAuthenticationChallenge:challenge]. But if I do that, I won't get a 401 response, but an error.
I've found no way to capture the first 401 response. Is there any way to do that?
1) For plain vanilla SSL without client certificate you don't need to implement these 2 methods
2) If you still want to, you should check for the HTTP response code in the [challenge failureResponse] object:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLCredential *urlCredential = [challenge proposedCredential];
NSURLResponse *response = [challenge failureResponse];
int httpStatusCode = -1;
if(response != nil) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
httpStatusCode = [httpResponse statusCode];
}
if(urlCredential != nil || httpStatusCode == 401) {
//wrong username or more precisely password, call this to create 401 error
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
else {
//go ahead, load SSL client certificate or do other things to proceed
}
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return YES;
}
If all else fails, try this: a wonderful library available, called AFNetworking, which is very easy to implement.
It uses blocks, which greatly simply communication of data between classes (does away with delegates), and is asynchronous.
Example usage is below:
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:"www.yourwebsite.com/api"]];
NSDictionary *params = @{
@"position": [NSString stringWithFormat:@"%g", position]
};
[client postPath:@"/api" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
As simple as that! Result is available directly within the class that calls the HTTP Post or Get method.
It even includes image and JSON requests, JSON deserialization, file download with progress callback, and so much more.