How to set "CURLOPT_USERPWD" field of libcurl in NSURLConnection - objective-c

I am trying to access a web service using NSURConnection, but i am not sure how to send password there because the example code given in php is setting CURLOPT_USERPWD field of libcurl, and there seems to be no such field in NSURLConnection/NSURLReqest.

Take the username and password, and concat them together with a colon. Take the resulting string and Base64 encode it. Take the Base64 encoded string, and prepend "Basic " (with the space). Now take that string (Basic [Base64 encoded value]) and set it as the "Authorization" header for your request.
Alternatively, if you're using the NSURLConnection delegate methods, you can implement one of them with something like:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSURLCredential * credential = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
(warning: untested, typed in a browser. caveat implementor)

Related

ASIHTTPRequest setDomain -> NSURLRequest equivalent?

I'm attempting to transition some old code for SOAP requests that relied heavily on the ASIHTTPRequest library, to instead use the standard NSMutableURLRequest instead. However, I'm running an issue: while with ASIHTTPRequest I had access to a setDomain function, I can't seem to find an equivalent with NSMutableURLRequest.
Does anyone know if there is an equivalent function? I've tried setting it as a header named "Domain", but that didn't seem to work.
Ended up following some advice from: iPhone - NTLM, Basic and other authorizations using async NSURLConnection
Turns out, you don't have to set the domain independently; you can set it by passing it as part of the username like so:
domain\\username
within an NSURLCredentials object. The exact code I ended up using was:
//takes care of HTTP Authentication
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSString* authMethod = [[challenge protectionSpace] authenticationMethod];
if ([authMethod isEqualToString:NSURLAuthenticationMethodNTLM]) {
NSURLCredential *credential = [NSURLCredential credentialWithUser:[NSString stringWithFormat:#"domain\\%#", self.userName]
password:self.password
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
}
This all assuming the class is an NSURLConnectionDeligate, of course.

NSURLCredential-credentialForTrust vs username:password

I am new with iOS and https stuffs. I am a beginning software student. I want to write an app that extract the data (such as class number, announcements, etc) from my uni account and show me on my app.
Normally, I go to my uni student page, login with my username and password, and those information showed up on the browser.
I've tried using NSURLConnection and its delegate. please look at my code below. In the beginning, in the method connection:didReceviveAuthenticationChallenge, I created an NSURLCredential object by [NSURLCredential withUser:password:persistence]. And it was not working. Then I tried to do NSLog, then i just found out that the protectionSpace.authenticationMethod is NSURLAuthenticationMethodServerTrust.
Then, I tried to read Apple's document and some googled sources. But, I can't really get it, and then I edited my code as you seen below (it is my final edition, and its still not working).
The point that i don't really get is: I expect that the server should ask me for my username and password. Instead of that, it asked for credentialForTrust, which based on those documents I've read, they suggest me to evaluate the trust of NSURLConnection delegate against the server. However, the server never asked for username and password. so, how can the server know which account I am accessing.
So, i think it should be some relationship between that certificate(trust) with username and password. I don't really understand how these things work.
I think, my question might not be really clear or something, and it might be foolish. I accept this because I've never learned all these things.
So, please someones explain me of how these things work. You can assume that I have some basic understanding of what is https, SSL, NSURLConnection, NSURLCredential, etc. and please guide me to the solution.
I am appreciate for all your efforts.
Below is my code, (it's not working).
The NSLog() in connection:didReceiveAuthenticationChallenge, print out "NSURLAuthenticationMethodServerTrust"
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"authenticationMethod is: %#\n",challenge.protectionSpace.authenticationMethod);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}else{
NSURLCredential *creden = [[NSURLCredential alloc] initWithUser:myusername password:mypassword persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:creden forAuthenticationChallenge:challenge];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"didFailWithError: %#\n %#\n",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.myData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.myData setLength:0];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *str = [[NSString alloc] initWithData:self.myData encoding:NSASCIIStringEncoding];
NSLog(#"Data in String %#", str);
}
NSURLAuthenticationMethodServerTrust challenges occur when the URL Loading system doesn't trust the server. If you want to avoid those calls, make sure that iOS trusts your server's cert (by installing it manually , or having a trusted root like Verisign sign it).

Street address verification

My user enters a recipients address (Street address not email). I need to verify it with the USPS so I know that it is actually an address.
I am digging through their API right now and I think I understand it but I'm not exactly sure how to go about it with objective-c.
So pretty much it works like so:
I have to create an XML request that contains the recipient name, address, and zip code.
I have to post that to their server
They respond with an XML response
Here is an example of what one of their constructed XML request looks like:
http://SERVERNAME/ShippingAPITest.dll?API=Verify&XML=<AddressValidateRequest% 20USERID="xxxxxxx"><Address ID="0"><Address1></Address1>
<Address2>6406 Ivy Lane</Address2><City>Greenbelt</City><State>MD</State> <Zip5></Zip5><Zip4></Zip4></Address></AddressValidateRequest>
A bit garbled but broken down:
http://SERVERNAME/ShippingAPITest.dll?API=Verify&XML=
<AddressValidateRequest% 20USERID="xxxxxxx">
<Address ID="0">
<Address1></Address1>
<Address2>6406 Ivy Lane</Address2>
<City>Greenbelt</City>
<State>MD</State>
<Zip5></Zip5>
<Zip4></Zip4>
</Address>
</AddressValidateRequest>
My first idea seems obvious but there maybe a better way to go about it. Since the XML feed short, should I go about construction by simple doing something along the lines of:
NSString *request = [NSString stringWithFormat:#"......"]
Where it is filled in and formatted along the lines posted above.
The second question is how to go about correctly sending this to the server?
I simply create a NSURL request and with the URL as the constructed XML string?
Here what I have but I keep getting that the URL was constructed wrong:
- (void)verifyAddress:(Recipient*)_recipient {
NSURL *_url = [NSURL URLWithString:#"http://testing.shippingapis.com/ShippingAPITest.dll?API=Verify&XML=<AddressValidateRequest%20USERID=\"********\"><Address ID=\"0\"><Address1></Address1><Address2>6406 Ivy Lane</Address2><City>Greenbelt</City><State>MD</State><Zip5></Zip5><Zip4></Zip4></Address></AddressValidateRequest>"];
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:_url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [NSMutableData data];
NSString* newStr = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding];
NSLog(#"the response '%#'", newStr);
} else {
// Inform the user that the connection failed.
NSLog(#"error");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString* newStr = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding];
NSLog(#"the response '%#'", newStr);
// do something with the data
// receivedData is declared as a method instance elsewhere
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
}
I get the following error:
Connection failed! Error - bad URL (null)
My only question now is, am I doing everything ok as far as NSURLConnection goes? I can play around with the URL, I just want to make sure my implementation is ok so Im not running around in circles. :P
You have % 20 in your URL. It should be %20 (no space).
There may be other problems, but that was one was easy to spot. If you are getting an error message, you need to edit your question and paste in the exact error message.
Also, you might consider using Apple's NSURLRequest and NSURLConnection classes, because more people are likely to be familiar with them so it may be easier for you to find help.
Cory, I work in the address validation industry (for SmartyStreets, where what you're trying to do is our specialty in fact) and have seen a lot of similar issues to yours.
We actually used to support an XML-endpoint for our address verification API (LiveAddress). Last year we deprecated it and deployed a new JSON format because the XML was clunky to use and had a lot of problems when it's actually just a simple task (for you, the developer).
So a few things to keep in mind... and while Rob's answer is programmatically comprehensive, these are important to consider also:
The USPS is the official source of addresses for the USA, but its core domain is not providing API service. Especially with recent financial troubles, I suspect that support and maintenance of the API will wane over time.
The License Agreement for the API you're using is quite restrictive. For example:
User agrees to use the USPS Web site, APIs and USPS data to facilitate USPS shipping transactions only. [27 Jan 2012]
Meaning, if you're shipping mail or packages via the USPS by using their API, it's fine, but for any other purpose it's not allowed and it violates the TOS.
I see you're developing for iOS. There's a great JSON library for that called TouchJSON that, in my opinion, is easier to use than XML formats in Objective-C.
While the USPS service does work, they CASS-certify private entities to provide their data at a better value (more specialty, experience, features, etc).
These and other maladies can be remedied by service from a third-party vendor. More details and reasons are documented here. Which provider you choose is up to you, but I'll be happy to personally answer any other address-validation-related questions.

verify a SSL certificate in iOS

I created a WCF web service hosted using https, I need to access this service using iPhone application.
The service works fine I can retrieve the data using a browser. which in this case, I need to pre-approve the certificate("The certificate for this server is invalid")
But in the iPhone app, I couldn't make it work.
What I have done so far is:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
[[challenge sender]continueWithoutCredentialForAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"%#",error.description); }
the connection returns with error(says exactly what a browser says)
The request should return a string or something really simple.
How do I proceed at this point? Btw, I use iOS5.0
Thanks
Found it, only need to add one more line into the connection willSendRequestForAuthenticationChallenge method
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

Can I use NSURLCredentialStorage for HTTP Basic Authentication?

I have a cocoa class set up that I want to use to connect to a RESTful web service I'm building. I have decided to use HTTP Basic Authentication on my PHP backend like so…
<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
//Stuff that users will see if they click 'Cancel'
exit;
}
else {
//Validation Code
echo "You entered info.";
}
?>
At this point I'm using a synchronous NSURLConnection, which I understand the Apple documentation states has less support for Authentication.
But is it even possible at all? I can do cookie authentication very easily sans NSURLProtectionSpaces or NSURLCredentials or any of the authentication classes. Also, are there any resources where I can read more about the Cocoa Authentication classes?
Thanks.
UPDATE: mikeabdullahuk
The code you supplied (the second example) is almost identical to what I had written. I have done some more investigating, and discovered that the NSURLConnection is returning an error…
Error Domain=NSURLErrorDomain Code=-1012 UserInfo=0x1a5170 "Operation could not be completed. (NSURLErrorDomain error -1012.)"
The code corresponds to NSURLErrorUserCancelledAuthentication. So apparently my code is not accessing the NSURLCredentialStorage and instead is canceling the authentication. Could this have anything to do with the PHP HTTP Authentication functions? I'm quite confused at this point.
A synchronous NSURLConnection will absolutely work with NSURLCredentialStorage. Here's how things usually work:
NSURLConnection requests the page from the server
The server replies with a 401 response
NSURLConnection looks to see what credentials it can glean from the URL
If the URL did not provide full credentials (username and password), NSURLConnection will also consult NSURLCredentialStorage to fill in the gaps
If full credentials have still not been determined, NSURLConnection will send the -connection:didReceiveAuthenticationChallenge: delegate method asking for credentials
If the NSURLConnection now finally has full credentials, it retries the original request including authorization data.
By using the synchronous connection method, you only lose out on step 5, the ability to provide custom authentication. So, you can either pre-provide authentication credentials in the URL, or place them in NSURLCredentialStorage before sending the request. e.g.
NSURLRequest *request =
[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://user:pass#example.com"]];
[NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
or:
NSURLCredential *credential = [NSURLCredential credentialWithUser:#"user"
password:#"pass"
persistence:NSURLCredentialPersistenceForSession];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:#"example.com"
port:0
protocol:#"http"
realm:nil
authenticationMethod:nil];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential
forProtectionSpace:protectionSpace];
[protectionSpace release];
NSURLRequest *request =
[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://example.com"]];
[NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
In a situation where a 401 or other authentication challenge is unacceptable/impossible, I sometimes use a dummy CFHTTPMessage to generate the authetication line, then copy that back into the NSURLRequest:
// assume NSString *username and *password exist and NSURLRequest *urlRequest
// exists and is fully configured except for HTTP Basic Authentication..
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);
[urlRequest setValue:authorizationString forHTTPHeaderField:#"Authorization"];
This may seem completely a bizarre way to do it but it is tolerant of situations where the username/password aren't URL clean and where NSURLRequest refuses to consult the NSURLCredentialStorage because the server isn't actually sending a HTTP 401 (for example it sends a regular page instead).
I would note mikeabdullahuk's answer is good but also if you use NSURLCredentialPersistencePermanent instead of per session it will store the credentials in the users keychain so next time you can check NSURLCredentialStorage for a non nil value for the default credentials for a protection space and if you get a non nil value you can just pass the credentials in. I am using this method right now for a delicious.com client I am writing and it works very well in my tests.
Set your credential as the default credential for the protectionspace:
// Permananent, session, whatever.
NSURLCredential *credential = [NSURLCredential credentialWithUser:username password:password persistence: NSURLCredentialPersistencePermanent];
// Make sure that if the server you are accessing presents a realm, you set it here.
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:#"blah.com" port:0 protocol:#"http" realm:nil authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
// Store it
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace];
At this point, any subsequent NSURLConnection that is challenged using a protection space that matches what you set will use this credential