iPhone - NTLM, Basic and other authorizations using async NSURLConnection - ios

Here's an issue: I need to implement both HTTP basic authorization and MS NTLM authorization. I use asynchronous NSURLConnection so I receive
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
callback. The full code of this method looks like that:
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSString* authMethod = [[challenge protectionSpace] authenticationMethod];
NSLog(#"Authentication method: %#", authMethod);
NSString *userName = self.login;
if ([authMethod isEqualToString:NSURLAuthenticationMethodNTLM]) {
userName = [userName lastElementAfterSlash];
}
else if ([authMethod isEqualToString:NSURLAuthenticationMethodNegotiate]) {
userName = [NSString stringWithFormat:#"%##%#", userName, self.url.host];
}
if ([challenge previousFailureCount] <= 1) {
NSURLCredential *credential = [NSURLCredential credentialWithUser:userName password:self.password persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:[self.url host]
port:0
protocol:self.url.scheme
realm:[self.url host]
authenticationMethod:authMethod];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential
forProtectionSpace:protectionSpace];
}
else {
NSLog(#"Authentication error");
NSLog(#"Failed login with status code: %d", [(NSHTTPURLResponse*)[challenge failureResponse]statusCode]);
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
The two global issues I've met so far using this method:
1) As per Charles sniffer, all the requests to the server are doubled (as per Apple Docs, it's an expected behaviour). However, it leads to lack of performance (two requests instead of just one) comparing to setting header directly, using setValue:forHTTPHeader:.
2) It's being called not for every request. For example, when I'm trying to grab an image from server, it returns 302 (redirect to login web page) HTTP code instead of 401 HTTP code, so this does not work at all.
What I want to accomplish, is to grab the correct Authorization header once and then put it manually in every subsequent NSMutableURLRequest I make. I can compose HTTP Basic authorization manually, it's pretty simple, but I can't compose NTLM header in the same manner, that's why I was relying on didReceiveAuthenticationChallenge: method.
Could you please point me out, how can I receive the Authorization header, that is set automatically by NSURLProtectionSpace, so each request will INITIALLY go authorized?
P.S. I tried to receive it from
[connection.currentRequest allHTTPHeaderFields];
but it returns an empty array. I'm fighting with that for more than two days, any help will be kindly appreciated!

Related

iOS useCredential: forAuthenticationChallenge is not using given credentials

I have the somewhat unusual use case and apple's useCredential:forAuthenticationChallenge can't cope (or maybe it's me?).
The issue is, that I am making two consecutive connections (the second is called after the first one has been completed) each with different credentials (different client certificate):
[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
URLs of requests are:
https://my.url.com/ws/service1
https://my.url.com/ws/service2
Then I implement the delegate's method:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge previousFailureCount] != 0) {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
NSURLCredential *newCredential = nil;
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
if (isFirstRequest) {
[[challenge sender] useCredential:credentials1 forAuthenticationChallenge:challenge];
} else {
[[challenge sender] useCredential:credentials2 forAuthenticationChallenge:challenge];
}
}
}
Credentials are created from identity (persistence flag has no effect whatsoever):
NSURLCredential* credential = [NSURLCredential credentialWithIdentity:identityRef
certificates:certificates
persistence:NSURLCredentialPersistenceNone];
I'm sure that both credentials are valid and that they should be working. But only the first call is successful. After that when the second request is made, willSendRequestForAuthenticationChallenge method is called and the correct useCredential:forAuthenticationChallange is called but it does not change the credentials and the first ones are used anyway!
From the documentation of - useCredential:forAuthenticationChallenge:
Attempt to use a given credential for a given authentication challenge. (required)
This method has no effect if it is called with an authentication challenge that has already been handled.
Is there a way to check if the challenge has already been handled or to reset the challenge so that the system does not use any cached credentials?
I already tried to erase cached credentials but the code below (from SO) always returned zero credentials:
- (void)eraseCredentials {
NSString *urlString = #"my.url.com";
NSURLCredentialStorage *credentialsStorage = [NSURLCredentialStorage sharedCredentialStorage];
NSDictionary *allCredentials = [credentialsStorage allCredentials];
if ([allCredentials count] > 0)
{
for (NSURLProtectionSpace *protectionSpace in allCredentials)
{
if ([[protectionSpace host] isEqualToString:urlString])
{
NSDictionary *credentials = [credentialsStorage credentialsForProtectionSpace:protectionSpace];
for (NSString *credentialKey in credentials)
{
[credentialsStorage removeCredential:[credentials objectForKey:credentialKey] forProtectionSpace:protectionSpace];
}
}
}
}
}
Note: What I already tried is to add the . or # after the URL as mentioned in TLS Session Cache or here at StackOverflow but I don't think that's the issue here because my willSendRequestForAuthenticationChallenge is called correctly multiple times as expected...jest the given credentials doesn't work.
Update: the request itself is being built as follows:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[self resourceURLWithName:name]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:30];
[request setHTTPShouldHandleCookies:NO];
Previously it was only:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[self resourceURLWithName:name]];
But there was no change in behaviour.
Update 2: In the end it wasn't issue with useCredential:forAuthenticationChallenge: but it was an issue with identities. The one I used seemed to be ok but it wasn't. See my answer in this SO question
Implement below delegate method
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
return NO;
}

can we implement HTTP Digest Proxy over SSL on iPhone programmatically, is it possible?

I am trying to implement Http Digest Authentication over SSL for one of my clients. I could not find any reference related to this. Is this possible to implement?
Thanks
After several research, I found out that, iOS does not provide any such in built support for Http authentication over SSL, expect SOCKS. But if you are desperate to implement it, create a socket and do Digest/NTLM/... handshake your self and use the same socket to create Input/Output streams. As of now, this is the only way to do it.
However, for regular Http requests such as POST/GET/... you can use NSURLConnection class as following code to authenticate the servers.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"InShiva didReceiveAuthenticationChallenge") ;
if ([challenge previousFailureCount] == 0)
{
NSLog(#"received authentication challenge");
NSURLCredential *newCredential = [NSURLCredential credentialWithUser:#"username"
password:#"password"
persistence:NSURLCredentialPersistenceForSession];
NSLog(#"credential created");
[[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
NSLog(#"responded to authentication challenge");
}
else
{
NSLog(#"previous authentication failure");
}
}

fetching an image from a https link

I have just started learning ios development, and I am trying to get an image from website which uses ssl, when i connect to the site through a browser(laptop) there is a warning which says that the root certificate is not trusted, I am not the owner of the website, however I can fully trust it.
My first attempt:
self.eventImage.image = [UIImage imageWithData:
[NSData dataWithContentsOfURL:
[NSURL URLWithString:imageUrl]]];
so I get this error
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9807)
I have tried to send users to the picture link by starting ios web browser, when they do that, they would get a message asking them if they could trust it or not, if they hit yes the image will appear, however i want the image to appear inside the application.
I have also tried to use web view but it didn't work.
Most of the similar questions in here suggested using this
- (BOOL)connection:(NSURLConnection *)
connection canAuthenticateAgainstProtectionSpace:
(NSURLProtectionSpace *)protectionSpace {
return NO;
//return [protectionSpace.authenticationMethod isEqualToString:
// NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)
connection didReceiveAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge {
NSString *imageUri =[self.detailItem objectForKey: #"image"];
NSArray *trustedHosts = [[NSArray alloc]initWithObjects:imageUri, nil];
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust])
if ([trustedHosts containsObject:challenge.protectionSpace.host])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust] forAuthenticationChallenge:
challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
but these two methods were never called when I add them.
Try adding these two methods
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
Change your code. Instead of using NSData dataWithContentsOfURL: you need to use your own explicit NSURLConnection. Then you can make use of the appropriate NSURLConnectionDelegate methods.
Another option is to use the popular AFNetworking library.
rmaddy, user2179059, and Anindya Sengupta answers helped in resolving this issue.
first, i used NSURLConnection explicitly, and for the secure connection i used this approach ( got it from this blog post )
-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:
(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust];
}
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// instead of XXX.XXX.XXX, add the host URL,
// if this didn't work, print out the error you receive.
if ([challenge.protectionSpace.host isEqualToString:#"XXX.XXX.XXX"]) {
NSLog(#"Allowing bypass...");
NSURLCredential *credential = [NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential
forAuthenticationChallenge:challenge];
}
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
this differ from user2179059 by limiting unsecure connection to that host only.
I hope you have included the NSURLConnectionDelegate protocol in the interface in .h file?
#interface ConnectionExampleViewController : UIViewController <NSURLConnectionDelegate>

ios https and basic authentication and post request

I'm trying to get the three above working properly, and something is not clicking. Specifically, the authentication request is not being triggered when it appears it should be. According to what I've read here, the relevant pieces are:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
NSLog(#"protection space");
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"auth challenge");
NSInteger count = [challenge previousFailureCount];
if (count > 0)
{
NSLog(#"count > 0");
NSObject<ServiceDelegate> *delegate = [currentRequest delegate];
[[challenge sender] cancelAuthenticationChallenge:challenge];
if ([delegate respondsToSelector:#selector(wrapperHasBadCredentials:)])
{
[delegate rbService:self wrapperHasBadCredentials:self];
}
return;
}
NSArray *trustedHosts = [[NSArray alloc] initWithObjects:#"myserver", nil];
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
NSLog(#"server trust");
if ([trustedHosts containsObject:challenge.protectionSpace.host])
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
else
{
NSLog(#"credentials");
NSURLCredential* credential = [[[NSURLCredential alloc] initWithUser:#"xxx" password:#"xxx" persistence:NSURLCredentialPersistenceForSession] autorelease];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
}
The target server is set up for two URLS, one HTTPS and one HTTP, both of which prompt for username/password using basic authentication. I've checked the target server using firefox and everything seems to work as advertised. The target server uses an untrusted cert, but I thought I'd taken care of that in the code. Maybe not.
The specific behavior in the application:
When using HTTP the log reads:
log - protection space
(then returns 401 code)
When using HTTPS:
log - protection space
log - auth challenge
log - server trust
log - protection space
(then returns a 401 code)
In the first instance, it gets to the canAuthenticate... section, returns, but then doesn't challenge for the authentication, and returns a 401 response.
In the second instance, it does all that, actually does challenge, then goes to the canAuthenticate... section again, returns, and returns a 401 response.
Keep in mind that the request is a POST request, complete with headers and an HTTPBody. The authentication is not included in the headers (I would rather not do that), but if there is no other solution I will try things that way.
As always, thank you very much for the help. It's priceless.
It looks like the answer here has to be sending the authentication along with the POST request. I was thinking that the challenge/response process would somehow add those headers to the request by itself, but apparently not.

In iOS, how would you programmatically pass a username / password to a secure site

How can you, in iOS, programmatically access a password protected site that has been protected by .htaccess or access control.
Using NSURLRequest, you can make a GET request from a URL. But if the URL is protected how would you send the username password pair. Also, what if it's encrypted with standard MD5 encryption.
Add something like this to your HTTPConnectionDelegate:
-(void)connection:(NSURLConnection *)aConnection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge previousFailureCount] == 0) {
NSURLCredential *newCredential;
newCredential = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}

Resources