NSURLConnection returning error instead of response for 401
Asked Answered
S

4

4

I have a web API that, for a specific request returns status code 200 if everything went ok, and 401 if the user is not logged in based on an Authorization token. Everything works fine if the response status is 200, but doesn't seem to work properly if the response status is 401, returning a connection error with code -1012, while the response is nil.

So, the following code:

[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    NSLog(@"%@", response);
    NSLog(@"%@", connectionError);

    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
    int statusCode = (int)[httpResponse statusCode];
    NSLog(@"response status code: %d", statusCode);

will display

2015-04-01 15:58:18.511 MyProject[3618:694604] <NSHTTPURLResponse: 0x155facc0> { URL: *SOME_URL* } { status code: 200, headers {
    "Access-Control-Allow-Headers" = "Content-Type, Accept, X-Requested-With";
    "Access-Control-Allow-Methods" = "POST, GET, PUT, UPDATE, OPTIONS";
    "Access-Control-Allow-Origin" = "*";
    Connection = "keep-alive";
    "Content-Type" = "application/json";
    Date = "Wed, 01 Apr 2015 12:58:14 GMT";
    Server = "Wildfly 8";
    "Transfer-Encoding" = Identity;
    "X-Powered-By" = "Undertow 1";
} }
2015-04-01 15:58:18.513 MyProject[3618:694604] (null)
2015-04-01 15:58:18.513 MyProject[3618:694604] response status code: 200

if the response status is 200, while if the status code is 401, I will get:

2015-04-01 16:05:55.988 MyProject[3633:695836] (null)
2015-04-01 16:05:55.992 MyProject[3633:695836] Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" UserInfo=0x146137c0 {NSErrorFailingURLKey=*SOME_URL*, NSErrorFailingURLStringKey=*SOME_URL*, NSUnderlyingError=0x1459e6d0 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error -1012.)"}
2015-04-01 16:05:55.992 MyProject[3633:695836] response status code: 0

If I do the same request using Postman or an Android device, I will get status code 401 with the following headers(copied from Postman):

Connection → keep-alive
Content-Length → 30
Content-Type → application/json
Date → Wed, 01 Apr 2015 13:07:34 GMT
Server → Wildfly 8
X-Powered-By → Undertow 1

Is there any fix or maybe a library that could give me some accurate response status? I searched a bit about the -1012 error, but couldn't find much and I don't really want to base on that.

Edit: after a bit of research I found the following statement on Appl's documentation: "If authentication is required in order to download the request, the required credentials must be specified as part of the URL. If authentication fails, or credentials are missing, the connection will attempt to continue without credentials."

But then how can I know if this error will be after a 401 status? Can it appear after another type of request?

Sentinel answered 1/4, 2015 at 13:17 Comment(0)
V
1

In order to get the 401 status code, I think you'll need to implement protocol NSURLConnectionDelegate and then connection:didReceiveAuthenticationChallenge:.

So, you'll also need to pass the delegate, maybe using [NSURLConnection connectionWithRequest:request delegate:self].

And, if you aren't trying to implement the authentication challenge, I would rather always return the 200 status code, but with different json content.

Hope it can help.

Vereen answered 1/4, 2015 at 13:39 Comment(5)
as i can see, connection:didReceiveAuthenticationChallenge: is deprecated for os.x(probably in the future for ios as well), i think connection:willSendRequestForAuthenticationChallenge: will help me as well, right? Where do I get the response then?Sentinel
will get the response with connection:didReceiveResponse:, I guess.Sentinel
You can use connection:didReceiveResponse, connection:didReceiveData and connectionDidFinishLoading. So, you need to implement NSURLConnectionDataDelegate protocol as well.Vereen
Do you happen to know if the connectionError with code -1012 is specific for this thing? Can, by any means, get the error in some other way? It would be a lot easier to treat the error as a 401 response for now because I have a lot of different requests. Unfortunatelly, marking all the responses from the server with 200 is not an option for now.Sentinel
Unfortunately, I think this is by design :(. I think I've already seen same behavior happening on c#, where you need to cast the exception and then get the response.Vereen
U
2

to check the 401 error, you can do this:

if (error != nil && error.code == NSURLErrorUserCancelledAuthentication) {
    // do something for 401 error
}

hope this help

Urbain answered 21/8, 2015 at 10:26 Comment(0)
V
1

In order to get the 401 status code, I think you'll need to implement protocol NSURLConnectionDelegate and then connection:didReceiveAuthenticationChallenge:.

So, you'll also need to pass the delegate, maybe using [NSURLConnection connectionWithRequest:request delegate:self].

And, if you aren't trying to implement the authentication challenge, I would rather always return the 200 status code, but with different json content.

Hope it can help.

Vereen answered 1/4, 2015 at 13:39 Comment(5)
as i can see, connection:didReceiveAuthenticationChallenge: is deprecated for os.x(probably in the future for ios as well), i think connection:willSendRequestForAuthenticationChallenge: will help me as well, right? Where do I get the response then?Sentinel
will get the response with connection:didReceiveResponse:, I guess.Sentinel
You can use connection:didReceiveResponse, connection:didReceiveData and connectionDidFinishLoading. So, you need to implement NSURLConnectionDataDelegate protocol as well.Vereen
Do you happen to know if the connectionError with code -1012 is specific for this thing? Can, by any means, get the error in some other way? It would be a lot easier to treat the error as a 401 response for now because I have a lot of different requests. Unfortunatelly, marking all the responses from the server with 200 is not an option for now.Sentinel
Unfortunately, I think this is by design :(. I think I've already seen same behavior happening on c#, where you need to cast the exception and then get the response.Vereen
C
0

I have a web API that, for a specific request returns status code 200 if everything went ok, and 401 if the user is not logged in based on an Authorization token. Everything works fine if the response status is 200, but doesn't seem to work properly if the response status is 401, returning a connection error with code -1012, while the response is nil.

I've run exactly into the same problem, REST API call returns 401 in Android and Postman, but status code 0 in iOS with a connection error with code -1012.

You can find more information about this problem in this SO post.

Seems to be an iOS bug (or at least very strange approach) happening both with async and sync requests.

I'm posting this just in case this might be useful for others bumping into the same issue. What I did in the code to manage the 401 status, right after the request, is to call a method that checks each managed http status code.

The method receives (NSError **)error and [(NSHTTPURLResponse *)response statusCode].

This is the 401 part - uses the error details stored inside userInfo structure.

// Auth failed or token problems
if (statusCode == 0 && [error userInfo] != nil) {

    // Get error detailed informations
    NSDictionary *userInfo = [error userInfo];
    NSString *errorString = [[userInfo objectForKey:NSUnderlyingErrorKey] localizedDescription];

    // If error message is of type authFailed or returns iOS code -1012 meaning auth failed
    if ([errorString containsString:@"kCFErrorDomainCFNetwork"] || [errorString containsString:@"-1012"]) {
        NSLog(@"%@ - ConnectionError - Code 401 - Cause: authentication failed, token is invalid or expired", type);
    } else {
        NSLog(@"%@ - ConnectionError - Cause: generic auth error", type);
    }

    // Alert user that auth failed
    ...
}

The other way is to check directly for this error code (-1012) as suggested by @ThuanDINH above. (edited the answer for objective c).

My code can be changed into:

// Auth failed - token problems
if (statusCode == 0 && [error code] == NSURLErrorUserCancelledAuthentication) {

    // If error code is UserCanceledAuthentication
    MPLog(MPLogLevelInfo, @"%@ - ConnectionError - Cause: authentication failed, token is invalid or expired", type);

    // Alert user that auth failed
    ...
}

But in this way you will not handle all other errors (you need to switch on each NSURLError code).

Here you can find the SO question with the list of all the NSURLError codes.

Carrew answered 1/3, 2016 at 16:2 Comment(0)
X
0

Based on Apple documentation https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html,

Note: NSURLSession does not report server errors through the error parameter. The only errors your delegate receives through the error parameter are client-side errors, such as being unable to resolve the hostname or connect to the host. The error codes are described in URL Loading System Error Codes.
Server-side errors are reported through the HTTP status code in the NSHTTPURLResponse object. For more information, read the documentation for the NSHTTPURLResponse and NSURLResponse classes.

We need to make sure that we do not cancel the session in our code. For example, I was calling NSURLSessionAuthChallengeCancelAuthenticationChallenge in didReceiveChallenge delegate method, when previousFailureCount > 1. This was suppressing 401 response and also call to didReceiveResponse.

When I changed above value to NSURLSessionAuthChallengePerformDefaultHandling, I am receiving 401 Unauthorized response in didReceiveResponse delegate method and works as expected.

Xenon answered 10/5, 2017 at 17:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.