I'm creating NSURLConnection with a link https://www.wella.com which is finally passed to a UIWebView
NSURL *url = [NSURL URLWithString:[[u stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
self.authRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:30];
self.authConnection = [NSURLConnection connectionWithRequest:authRequest delegate:self];
I am unexpectedly receiving authentication challenge, which of course I am currently not handling.
I've noticed that when using desktop browser, when I paste the link above, it's address is automatically changed to https://www.wella.com/professional/countryselector
When pasting the extended link, NSURLConnection works without any problems.
How can I get rid of this challenge and could it be somehow connected with that link auto change?
EDIT: I've solved it. The question, however, remains: why does the auto link change causes calling the authentication challenge?
Thanks to: Muralikrishna's answer
I've managed it by implementing the NSURLConnectionDelegate method and setting the trust credential:
- (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
This issue is all over Stack Overflow and I have spent the past 2 days trying countless combinations of ATP configurations and getting my app to work. I'm going to be thorough with my problem as it appears the tiniest thing can affect how to resolve this.
I have just recently set up an Ubuntu 14 server with SSL and TLS 1.2 enabled. On my server are my server-side scripts which my app depends on. In my app I use a NSMutableURLRequest to request my API from the server like so:
NSString * noteDataString = [NSString stringWithFormat:#"email=%#&password=%#&type=%#", companyEmail, companyPassword, #"login"];
NSData * postData = [noteDataString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString * postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[postData length]];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"https://mydomain.me:443/path/to/file.php"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
NSHTTPURLResponse * urlResponse = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];
NSString * result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"Response Code: %ld", (long)[urlResponse statusCode]);
When I copy the url into Chrome the correct PHP return is shown. When requesting from Xcode 7 on iOS 9 I get this error:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
I have used countless info.plist settings as seen across similar issues. I have tried disabling the need for forward secrecy, I have tried enabling arbitrary loads, I have tried using the exception domain mydomain.me and its subdomains. The only progress I achieve is an error code -9813 switching to -9802.
I am aware of adding delegate methods for NSURLRequest but these are never called and assumed redundant for solving this problem.
I built a lot of the app off a MAMP server using localhost and http and the requests worked when I enabled arbitrary loads then, so nothing wrong with my plist.
It's mind-boggling onto why I have a special case, and I knew Stack Overflow was the place for such situations!
Thanks, and I hope that this helps many more developers beyond me when solved.
Solution. Thank you everybody in the comments for pointing me in the right direction. The solution was to create an NSObject that handled NSURLRequests for this special case.
Credit to: https://www.cocoanetics.com/2010/12/nsurlconnection-with-self-signed-certificates/
The following is almost a direct copy from the tutorial in the link, but I figured it'd be easier to stay here.
So,
I had to create a new NSObject class with the following code:
BWWebService.h
#import <Foundation/Foundation.h>
#interface BWWebService : NSObject{
NSMutableData *receivedData;
NSURLConnection *connection;
NSStringEncoding encoding;
}
- (id)initWithURL:(NSURL *)url;
#end
BWWebService.m
#import "BWWebService.h"
#implementation BWWebService
- (id)initWithURL:(NSURL *)url{
if (self = [super init]){
NSURLRequest * request = [NSURLRequest requestWithURL:url];
connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
}
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
// Every response could mean a redirect
receivedData = nil;
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)
[response textEncodingName]);
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
if (!receivedData){
// No store yet, make one
receivedData = [[NSMutableData alloc] initWithData:data];
}else{
// Append to previous chunks
[receivedData appendData:data];
}
}
// All worked
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSString * xml = [[NSString alloc] initWithData:receivedData encoding:encoding];
NSLog(#"%#", xml);
}
// And error occurred
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Error retrieving data, %#", [error localizedDescription]);
}
// To deal with self-signed certificates
- (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]){
// we only trust our own domain
if ([challenge.protectionSpace.host isEqualToString:#"myuntrusteddomain.me"]){
NSURLCredential * credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
#end
And if that wasn't enough, to make the request I used the following in replacement of my original request:
NSURL * url = [NSURL URLWithString:#"https://myuntrusteddomain.me:443/path/to/script.php"];
BWWebService * webService;
webService = [[BWWebService alloc] initWithURL:url];
I know that this does not POST data like the original, but that comes later. I am sure it will be a matter of handling the POST in initWithURL.
Thanks everyone.
Edit: It appears this application of the solution only works with Allows Arbitrary Loads set to YES.
Open Xcode, Command-Shift-2, enter 9813, and you immediately find -9813 = errSSLNoRootCert, which was to be expected since your self signed certificate has no root certificate, while -9802 = errSSLFatalAlert (you really buggered it up).
The problem seems to be that for security reasons, some software doesn't like self signed certificates. This can often be fixed by creating and installing your own root certificate, and having a certificate signed by your own root certificate.
I'm building my first IOS Application and I'm currently integrating my application with my Prestashop website. I already have grabbed a ton of data from the databases, but I'm having less luck using the Search Method from their API.
I have successfully called the REST API and authenticated with the server. I'm getting back a response status code of 200 so everything looks ok with the authentication, however the data I'm receiving from the server is the homepage of my site in HTML form rather than an XML file with the search results.
Below I'll list some of the parts of code in question that I'm using to access the Prestashop server
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://www.******.com:443/api/search/?query=%#&language=1",searchTerm]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
This is the method I use to set up the connection to the Prestashop Server
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
NSLog(#"Test 1.5");
[self clearCookiesForURL];
if([challenge previousFailureCount]==0)
{
NSURLCredential *credential = [NSURLCredential credentialWithUser:#"(I'm omitting the credential key but it does work)" password:#"" persistence:NSURLCredentialPersistenceForSession];
NSLog(#"Credential : %#",credential);
//NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
} else{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
And this is the method for my authentication. I have all of the other connection methods instantiated (canAuthenticateAgainstProtectionSpace, didReceiveResponse, didReceiveData, etc.) and they are all being called and working fine.
NSLog(#"Data After : %#",responseData);
NSString *tempString;
tempString = [[NSString alloc] initWithData:responseData encoding:NSASCIIStringEncoding];
NSLog(#"String : %#",tempString);
This is the section of code I use to translate the data I get from the server into a string (which i thought was going to be XML but is instead HTML)
Here is a screenshot of the debug log showing that I'm getting an HTML File
Can anybody see what I'm doing wrong here? Any help would be greatly appreciated!
I am trying to load a secure website in a UIWebView my basic approach is to create a NSURL, the n a NSURLRequest, then a NSURLConnection, then to load the NSURLRequest in the UIWebView. When the website is loaded I receive
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
I respond to the challenge sender with
- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
But after that I get nothing... it just hangs. I put in break points so I know that
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
is being called. If I wait till I am sure that the NSURLConnection is not going to complete then reload the view no authentication challenge is sent but the view will load. I do not have any control over the server. I am open to using AFNetworking, but only if necessary.
The full listing of source code is provided below:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if ([challenge previousFailureCount] == 0)
{
NSString *username = #"username";
NSString *password = #"passsword";
NSURLCredential * cred = [NSURLCredential credentialWithUser:username
password:password
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:cred forAuthenticationChallenge:challenge];
}
else
{
}
}
-(void)updateCard
{
NSURL * url = [NSURL URLWithString:#"https://ssl.letu.edu/applications/chapelattendance/attendance.html"];
NSURLRequest * request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:50.0];
self.webView =[[UIWebView alloc] initWithFrame:self.bounds];
self.webView.delegate = self;
[self.webView loadRequest:request];
self.connection = [[ NSURLConnection alloc]initWithRequest:request delegate:self];
[self.connection start];
}
Where did I go wrong?
You need to first retrieve the "authentication method" the server is requesting for:
[[challenge protectionSpace] authenticationMethod]
These are the authentication methods (which are string constants) which the expression above returns:
NSURLAuthenticationMethodDefault
NSURLAuthenticationMethodHTTPBasic
NSURLAuthenticationMethodHTTPDigest
NSURLAuthenticationMethodHTMLForm
NSURLAuthenticationMethodNegotiate
NSURLAuthenticationMethodNTLM
NSURLAuthenticationMethodClientCertificate
NSURLAuthenticationMethodServerTrust
Then, you have these options:
If you want to provide the credentials for the given authentication method, you invoke
useCredential:forAuthenticationChallenge:
If you don't want to handle that authentication method yourself and want the system try
to authenticate, you may invoke performDefaultHandlingForAuthenticationChallenge:
which may then fail or not, depending whether the system is capable to handle that type
of authentication and whether it can find credentials in well known storages.
If you cant handle that authentication method -- say authentication method
NSURLAuthenticationMethodNTLM for example -- you can skip this protection
space and try another protection space if another one
exists in this authentication challenge. Then you may possibly get an
authentication method NSURLAuthenticationMethodHTTPBasic which you
are capable to handle.
In order to reject the current protection space you send method
rejectProtectionSpaceAndContinueWithChallenge: to the
authentication challenge sender. Then, NSURLConnection will send
once again willSendRequestForAuthenticationChallenge: to your
delegate with another protection space if any further exists.
You may try to continue without providing credentials at all.
Likely, the authentication will fail. You can try it through
sending message continueWithoutCredentialForAuthenticationChallenge:
to the authentication challenge sender.
And finally, you can cancel the request through canceling the
authentication challenge: send cancelAuthenticationChallenge: to
the authentication challenge sender.
Note: NSURLAuthenticationMethodHTTPBasic and NSURLAuthenticationMethodHTTPDigest authentication methods can be handled with the same NSURLCredential object created with +credentialWithUser:password:persistence:
If anyone comes along and has the same problem be sure I want to share the solution I found. Use AFNetworking.
Here is the revised code:
-(void)updateCard
{
if(!self.webView)
{
self.webView =[[UIWebView alloc] initWithFrame:self.bounds];
self.webView.delegate = self;
}
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
NSString *username = #"username";
NSString *password = #"password";
NSURL *url = [NSURL URLWithString:#"https://ssl.letu.edu/"];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL: url];
[client setAuthorizationHeaderWithUsername:username password:password];
NSMutableURLRequest *request = [client requestWithMethod:#"GET" path:#"applications/chapelattendance/attendance.html"
parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
[self.webView loadRequest:request];
}
failure: ^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Could not load chapel attendance");
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
}
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
operation.securityPolicy.allowInvalidCertificates = YES;
You need to send the username and password combination with the http header to authenticate the request while sending the same.
NSData *authData = [#"username:password" dataUsingEncoding:NSASCIIStringEncoding];
NSString *authorization = [NSString stringWithFormat:#"Basic %#", [authData base64Encoding]];
[mutableRequest addValue:authorization forHTTPHeaderField:#"Authorization"];
Im trying to make https post request by using following code.
NSURL *url = [NSURL URLWithString:#"https://portkey.formspring.me/login/"];
//initialize a request from url
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[url standardizedURL]];
//set http method
[request setHTTPMethod:#"POST"];
//initialize a post data
NSDictionary *postDict = [NSDictionary dictionaryWithObjectsAndKeys:#"username", #"username",
#"password", #"password", nil];
NSError *error=nil;
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:postDict
options:NSJSONWritingPrettyPrinted error:&error];
[request setValue:#"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
//set post data of request
[request setHTTPBody:jsonData];
//initialize a connection from request
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//start the connection
[connection start];
But im getting following response.
error Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “portkey.formspring.me” which could put your confidential information at risk." UserInfo=0x7564180 {NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSErrorFailingURLKey=https://portkey.formspring.me/login/, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending >to be “portkey.formspring.me” which could put your confidential information at risk., NSUnderlyingError=0x7168a20 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “portkey.formspring.me” which could put your confidential information at risk.", NSURLErrorFailingURLPeerTrustErrorKey=}
Can anybody tell me how to make post request using NSURLConnection??
Thanks in advance.
The certificate for the site you're trying to connect to is untrusted (try visiting the link you posted in Chrome).
By default, iOS won't let you connect to site that presents an untrusted certificate. You can bypass this check if absolutely necessary - see this question: How to use NSURLConnection to connect with SSL for an untrusted cert?
However, it's going to much, much, much better to actually fix the certificate in question.
These Delegate Methods of NSURLConnection are Deprecated
-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
You can use -connection:willSendRequestForAuthenticationChallenge: instead of these 3 Deprecated Methods
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust)
{
[[challenge sender] useCredential:[NSURLCredential credentialForTrust:[[challenge protectionSpace] serverTrust]] forAuthenticationChallenge:challenge];
}
}
I have searched google for the different method and libraries used to consume a RESTful web service. But most of them are either outdated or failed to work when I tried the old libraries. This could be due to the fact that some of there were discontinued and not compatible with the new SDK.
I have tried SBJSON, ASIHTTP, stig's and jsonframework, but non of them seem to be working.
What are some of the current libraries that are being used in iOS to consume RESTful webservice? It will be helpful if anyone can give link to sample tutorial using the same libraries.
Try RestKit: http://restkit.org/
It is well known, popular, consumes XML as well as JSON and works with Core Data (response to NSManagedObjects mapping) which make great backend as a local cache.
Why dont you use iOS API classes like NSURLConnection? (iOS5 or above required I think)
You could invoke REST GET opperation for example like this:
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
[req setHTTPMethod:GET];
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[url host]];//for https
connection = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
Where url should be an NSURL object pointing to your rest service operation url. And declare the corresponding delegate methods:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
code = [httpResponse statusCode];
NSLog(#"%# %i",#"Response Status Code: ",code);
[data setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)d {
[self.data appendData:d];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[[[UIAlertView alloc] initWithTitle:#"Error"
message:nil
delegate:nil
cancelButtonTitle:#"ok"
otherButtonTitles:nil] show];
self.connection = nil;
self.data = nil;
}
connection, data and code could be local variables to your implementation class. In those variables you are going to store the connection made, the JSON data received (or whatever) and the response http code like 200, 404.
And finally if you are planning to invoke a secured REST service, dont forget to include the authenticationchallenge delegate.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
//set the user and password loged in
NSString *username = #"username";
NSString *password = #"password";
NSURLCredential *credential = [NSURLCredential credentialWithUser:username
password:password
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
Hope this helps!
Use the built-in NSURLSession and NSJSONSerialization and then use Github's Mantle framework to map the JSON dictionary to your custom ObjC objects.