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];
}
Related
I am working with an azure API endpoint. ....azure-api.net/.... When I try to view a HTTP request / response using Charles proxy, the HTTP response is empty. The request works when I turn the proxy off.
I want to intercept the requests and mock the response for the purposes of automation tests.
I can view other HTTPs end-points to other servers using Charles proxy. So I believe there is something special about azure that is preventing the request from completing.
How does azure know there is a proxy in the middle and it is not talking to the client?
Is there anyway to configure the azure API to allow Charles to work? (Since viewing HTTP traffic is useful for development)
Is there another method that will allow the traffic to azure to be mocked? E.g. host redirect?
I am using standard iOS networking code
NSURL *url = [NSURL URLWithString:#"https://MyDomain.azure-api.net/a/b/2?subscription-key=myKey"];
[[[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:url]
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"%#, error: %#", [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding], error);
;
}] resume];
Below are screenshots from Charles Proxy. There is no response after the connect request.
In order to intercept the request and return a fake response, you could use OHHTTPStubs
To be enable to do it, you should either log the response when debugging from Xcode or use the documentation.
Before you execute your test, call
stub = [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return [request.URL.absoluteString isEqualToString:#"https://MyDomain.azure-api.net/a/b/2?subscription-key=myKey"];
}
withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
NSString *fixture = OHPathForFileInBundle(#"fakeResponse.json", nil);
return [OHHTTPStubsResponse responseWithFileAtPath:fixture
statusCode:200
headers:#{#"Content-Type":#"text/json"}];
}];
And after you execute your test you should call
[OHHTTPStubs removeStub:stub];
"fakeResponse.json" must be a file added only to your test target and you can have different tests with different JSON files. For example you can pass a JSON with an error or empty content to test if your application behaves the way you want it to behave.
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?
I am trying to login into a website through an HTML form using AFNetworking 1.3. I simply set my credentials and POST to the proper path. The problem is that I am not issued a cookie that states that I am logged in.
NSURL *baseURL = [NSURL URLWithString:#"https://mysite.edu"];
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:baseURL];
NSString *loginPath = #"/place/loginPage";
NSDictionary *loginParameters = #{#"sid" : #"username",
#"PIN" : #"12345678"};
NSMutableURLRequest *request = [client multipartFormRequestWithMethod:#"POST"
path:loginPath
parameters:loginParameters
constructingBodyWithBlock:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"success: %#", operation.responseString);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", operation.responseString);
}];
[operation start];
The web server is returning a webpage that states that I do not have cookies enabled:
This system requires the use of HTTP cookies to verify authorization information.
Our system has detected that your browser has disabled HTTP cookies, or does not support them.
Please refer to the Help page in your browser for more information on how to correctly configure your browser for use with this system.
However, if I iterate through my issued cookies, I find that the website has issued me cookies, but not a session cookie. Therefore my AFNetworking client really is accepting cookies? Is there a setting that I must adjust to allow session cookies to properly work with AFNetworking 1.3?
I have edited my HTTP request header to match working browsers such as Chrome.
[Is] my AFNetworking client really [] accepting cookies? Is there a setting that I must adjust to allow session cookies to properly work with AFNetworking 1.3?
AFNetworking has no cookie-specific configuration. Cookies are solely the responsibility of the Foundation URL Loading system.
Some possible solutions:
Install AFHTTPRequestOperationLogger and configure it with AFLoggerLevelDebug to see all HTTP headers. This will let you see what's actually getting sent and received in your Xcode console.
The server may be dropping the cookie, for example if it's a Ruby server with protect_from_forgery enabled. Check your server logs and this solution for information on disabling or sending the appropriate token.
You control how requests use cookies with -[NSMutableURLRequest setHTTPShouldHandleCookies]. Make sure this is set to YES (the default.)
Other apps can alter the Cookie Accept Policy, so make sure -[NSHTTPCookieStorage cookieAcceptPolicy] is returning NSHTTPCookieAcceptPolicyAlways (or NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain if you need it.)
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"];
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"];
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
}