Question

I'm trying to connect to http://cmis.demo.nuxeo.org/nuxeo/atom/cmis/ with NSURLConnection. This demo web service is documented to require authentication (login: Administrator / password: Administrator).

Edit: This web service now sends an authentication challenge, it was not at the time the question was asked.

This web service does not send an authentication challenge, so I can't use the connection:didReceiveAuthenticationChallenge: delegate method. Instead I set a default NSURLCredential in the shared credential storage. Unfortunately, this default credential is not used by NSURLConnection.

Here is my code (using ARC, tested on iOS 5):

@implementation ViewController
{
    NSMutableData *responseData;
}

- (IBAction) connect:(id)sender
{
    NSString *user = @"Administrator";
    NSString *password = @"Administrator";
    NSURL *nuxeoURL = [NSURL URLWithString:@"http://cmis.demo.nuxeo.org/nuxeo/atom/cmis/"];

    NSURLCredential *credential = [NSURLCredential credentialWithUser:user password:password persistence:NSURLCredentialPersistenceForSession];
    NSString *host = [nuxeoURL host];
    NSNumber *port = [nuxeoURL port];
    NSString *protocol = [nuxeoURL scheme];
    NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:host port:[port integerValue] protocol:protocol realm:nil authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
    [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:nuxeoURL];
    BOOL manuallyAddAuthorizationHeader = NO;
    if (manuallyAddAuthorizationHeader)
    {
        NSData *authoritazion = [[NSString stringWithFormat:@"%@:%@", user, password] dataUsingEncoding:NSUTF8StringEncoding];
        NSString *basic = [NSString stringWithFormat:@"Basic %@", [authoritazion performSelector:@selector(base64Encoding)]];
        [request setValue:basic forHTTPHeaderField:@"Authorization"];
    }
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [connection start];
}

- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    responseData = [NSMutableData data];
}

- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [responseData appendData:data];
}

- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"connectionDidFinishLoading:%@", connection);
    NSLog(@"%@", [[NSString alloc] initWithData:responseData encoding:NSISOLatin1StringEncoding]);
}

- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"connection:%@ didFailWithError:%@", connection, error);
}

@end

Since the default credential is not used, I receive html (login page) instead of xml. If I set the manuallyAddAuthorizationHeader variable to YES, then authorization works and I receive xml.

My question is: why does NSURLConnection does not automatically use the default NSURLCredential?

Was it helpful?

Solution

Sending the default credential without an authentication challenge is considered insecure, especially in the case where you're communicating over plain HTTP. The HTTP spec says that you can send credentials proactively, but that you should usually wait for an authentication challenge. And that's the behavior that Cocoa provides by default.

If you need to proactively send the credentials, then you'll have to manually add the headers yourself. You can just base-64 encode it yourself, or you can use the CFHTTP functions to do it for you, like so:

CFHTTPMessageRef dummyRequest =
    CFHTTPMessageCreateRequest(
        kCFAllocatorDefault,
        CFSTR("GET"),
        (CFURLRef)[urlRequest URL],
        kCFHTTPVersion1_1);
CFHTTPMessageAddAuthentication(
    dummyRequest,
    nil,
    (CFStringRef)username,
    (CFStringRef)password,
    kCFHTTPAuthenticationSchemeBasic,
    FALSE);
authorizationString =
    (NSString *)CFHTTPMessageCopyHeaderFieldValue(
        dummyRequest,
        CFSTR("Authorization"));
CFRelease(dummyRequest);

Then just set the authorizationString as the Authorization header in your NSURLRequest.

(Code copied blatantly from https://stackoverflow.com/a/509480/100478, though I've written similar code myself many times.)

OTHER TIPS

As HTTP supports many different authentication methods (Basic, Digest, Kerberos, etc), NSURLConnection cannot send the credentials before it receives the authentication challenge from the server because it does't know how to send them.

While BJ's answer may solve your problem on the client side, the real issue is the server. Internally NSURLConnection will use it's own equivalent of the delegate method connection:didReceiveAuthenticationChallenge: and the other authentication methods. This is where it looks for a credential it can apply from NSURLCredentialStorage . In your case this doesn't seem to be happening. For those methods to be invoked the server not only has to give a 40x HTTP status code, but it must include a WWW-Authenticate header. This tells NSURLConnection to attempt to answer the authentication challenge. It's likely that your server isn't including this, and that's why the client isn't sending credentials.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top