NSURLConnection and Basic HTTP Authentication in iOS

89,241

Solution 1

I'm using an asynchronous connection with MGTwitterEngine and it sets the authorization in the NSMutableURLRequest (theRequest) like so:

NSString *authStr = [NSString stringWithFormat:@"%@:%@", [self username], [self password]];
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]];
[theRequest setValue:authValue forHTTPHeaderField:@"Authorization"];

I don't believe this method requires going through the challenge loop but I could be wrong

Solution 2

Even the question is answered, I want to present the solution, which doesn't require external libs, I found in another thread:

// Setup NSURLConnection
NSURL *URL = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:URL
                                         cachePolicy:NSURLRequestUseProtocolCachePolicy
                                     timeoutInterval:30.0];

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
[connection release];

// NSURLConnection Delegates
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    if ([challenge previousFailureCount] == 0) {
        NSLog(@"received authentication challenge");
        NSURLCredential *newCredential = [NSURLCredential credentialWithUser:@"USER"
                                                                    password:@"PASSWORD"
                                                                 persistence:NSURLCredentialPersistenceForSession];
        NSLog(@"credential created");
        [[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
        NSLog(@"responded to authentication challenge");    
    }
    else {
        NSLog(@"previous authentication failure");
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    ...
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    ...
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    ...
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    ...
}

Solution 3

Here is a detailed answer with no 3rd party involved:

Please check here:

//username and password value
NSString *username = @“your_username”;
NSString *password = @“your_password”;

//HTTP Basic Authentication
NSString *authenticationString = [NSString stringWithFormat:@"%@:%@", username, password]];
NSData *authenticationData = [authenticationString dataUsingEncoding:NSASCIIStringEncoding];
NSString *authenticationValue = [authenticationData base64Encoding];

//Set up your request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.your-api.com/“]];

// Set your user login credentials
[request setValue:[NSString stringWithFormat:@"Basic %@", authenticationValue] forHTTPHeaderField:@"Authorization"];

// Send your request asynchronously
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *responseCode, NSData *responseData, NSError *responseError) {
      if ([responseData length] > 0 && responseError == nil){
            //logic here
      }else if ([responseData length] == 0 && responseError == nil){
             NSLog(@"data error: %@", responseError);
             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"Error accessing the data" delegate:nil cancelButtonTitle:@"Close" otherButtonTitles:nil];
             [alert show];
             [alert release];
      }else if (responseError != nil && responseError.code == NSURLErrorTimedOut){
             NSLog(@"data timeout: %@”, NSURLErrorTimedOut);
             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"connection timeout" delegate:nil cancelButtonTitle:@"Close" otherButtonTitles:nil];
             [alert show];
             [alert release];
      }else if (responseError != nil){
             NSLog(@"data download error: %@”,responseError);
             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"data download error" delegate:nil cancelButtonTitle:@"Close" otherButtonTitles:nil];
             [alert show];
             [alert release];
      }
}]

Kindly let me know your feedback on this.

Thanks

Solution 4

If you don't want to import the whole of MGTwitterEngine and you aren't doing an asynchronous request Then you can use http://www.chrisumbel.com/article/basic_authentication_iphone_cocoa_touch

To base64 encode the Username and password So replace

NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]];

with

NSString *encodedLoginData = [Base64 encode:[loginString dataUsingEncoding:NSUTF8StringEncoding]];

after

you will need to include the following file

static char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

@implementation Base64
+(NSString *)encode:(NSData *)plainText {
    int encodedLength = (((([plainText length] % 3) + [plainText length]) / 3) * 4) + 1;
    unsigned char *outputBuffer = malloc(encodedLength);
    unsigned char *inputBuffer = (unsigned char *)[plainText bytes];

    NSInteger i;
    NSInteger j = 0;
    int remain;

    for(i = 0; i < [plainText length]; i += 3) {
        remain = [plainText length] - i;

        outputBuffer[j++] = alphabet[(inputBuffer[i] & 0xFC) >> 2];
        outputBuffer[j++] = alphabet[((inputBuffer[i] & 0x03) << 4) | 
                                     ((remain > 1) ? ((inputBuffer[i + 1] & 0xF0) >> 4): 0)];

        if(remain > 1)
            outputBuffer[j++] = alphabet[((inputBuffer[i + 1] & 0x0F) << 2)
                                         | ((remain > 2) ? ((inputBuffer[i + 2] & 0xC0) >> 6) : 0)];
        else 
            outputBuffer[j++] = '=';

        if(remain > 2)
            outputBuffer[j++] = alphabet[inputBuffer[i + 2] & 0x3F];
        else
            outputBuffer[j++] = '=';            
    }

    outputBuffer[j] = 0;

    NSString *result = [NSString stringWithCString:outputBuffer length:strlen(outputBuffer)];
    free(outputBuffer);

    return result;
}
@end

Solution 5

Since NSData::dataUsingEncoding is deprecated (ios 7.0), you could use this solution:

// Forming string with credentials 'myusername:mypassword'
NSString *authStr = [NSString stringWithFormat:@"%@:%@", username, password];
// Getting data from it
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
// Encoding data with base64 and converting back to NSString
NSString* authStrData = [[NSString alloc] initWithData:[authData base64EncodedDataWithOptions:NSDataBase64EncodingEndLineWithLineFeed] encoding:NSASCIIStringEncoding];
// Forming Basic Authorization string Header
NSString *authValue = [NSString stringWithFormat:@"Basic %@", authStrData];
// Assigning it to request
[request setValue:authValue forHTTPHeaderField:@"Authorization"];
Share:
89,241

Related videos on Youtube

Alexi Groove
Author by

Alexi Groove

Updated on August 07, 2020

Comments

  • Alexi Groove
    Alexi Groove almost 4 years

    I need to invoke an initial GET HTTP request with Basic Authentication. This would be the first time the request is sent to the server and I already have the username & password so there's no need for a challenge from the server for authorization.

    First question:

    1. Does NSURLConnection have to be set as synchronous to do Basic Auth? According to the answer on this post, it seems that you can't do Basic Auth if you opt for the async route.

    2. Anyone know of any some sample code that illustrates Basic Auth on a GET request without the need for a challenge response? Apple's documentation shows an example but only after the server has issued the challenge request to the client.

    I'm kind of new the networking portion of the SDK and I'm not sure which of the other classes I should use to get this working. (I see the NSURLCredential class but it seems that it is used only with NSURLAuthenticationChallenge after the client has requested for an authorized resource from the server).

  • catsby
    catsby over 14 years
    Should have noted that I'm doing so in an OS X app and not an iPhone App, but hopefully it will still work for you
  • Alexi Groove
    Alexi Groove over 14 years
    Can you tell me what's the reason behind limiting the encoding line length to 80 in your example code? I thought that HTTP headers have a max length of something like 4k (or maybe some servers don't take anything longer than that).
  • catsby
    catsby over 14 years
    I didn't write that part, it's just part of MGTwitterEngine, from a category added to NSData. See NSData+Base64.h/m here: github.com/ctshryock/MGTwitterEngine
  • elim
    elim over 11 years
    For base64-encoding ([authData base64EncodedString]) add the NSData+Base64.h and .m File from Matt Gallagher to your XCode-Project (Base64 encoding options on the Mac and iPhone).
  • benzado
    benzado over 11 years
    This is not quite the same as the other solutions: this contacts the server first, receives a 401 response, then responds with the correct credentials. So you're wasting a round-trip. On the upside, your code will handle other challenges, such as HTTP Digest Auth. It's a trade-off.
  • lagos
    lagos over 11 years
    Anyway, this is the "correct way" of doing it. All the other ways is a shortcut.
  • user963601
    user963601 almost 11 years
    If you don't explicitly stop disk caching (and your server doesn't stop it with a Cache-Control header), this method can cause your request to be cached in Cache.db, which means your password will be accessible on disk.
  • LE SANG
    LE SANG almost 11 years
    Thanks so much! @moosgummi
  • Dirk de Kok
    Dirk de Kok over 10 years
    NSASCIIStringEncoding will corrupt non-usascii usernames or passwords. Use NSUTF8StringEncoding instead
  • bickster
    bickster over 9 years
    base64EncodingWithLineLength does not exist in the year 2014 on NSData. Use base64Encoding instead.
  • Dirk
    Dirk over 9 years
    @bickster base64Encodingis deprecated since iOS 7.0 and OS X 10.9. I use [authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWi‌​thLineFeed] instead. Also available are ` NSDataBase64Encoding64CharacterLineLength` or NSDataBase64Encoding76CharacterLineLength
  • Declan McKenna
    Declan McKenna over 8 years
    @dom I've used this but for some reason didRecieveAuthenticationChallenge is not being called and I'm getting a 403 access denied message back from the site. Anyone know what's gone amiss?
  • Ben
    Ben over 8 years
    The method base64Encoding you are using to convert NSData to NSString is now deprecated: - (NSString *)base64Encoding NS_DEPRECATED(10_6, 10_9, 4_0, 7_0); Better to use NSDataBase64Encoding category instead.
  • dgatwood
    dgatwood about 7 years
    You should not do that. Apple's docs say that it won't work, and it definitely has failed in many versions of iOS over the years, because the Authorization header is explicitly reserved for use by the URL loading system, and cannot safely be set in requests.
  • dgatwood
    dgatwood about 7 years
    Yes, this is the only correct way to do it. And it only causes a 401 response the first time. Subsequent requests to that same server are sent with authentication.