Objective C - Webkit external files causing 401 - objective-c

I'm currently using Webkit to display a mobile web on my iPhone app.
The domain is password protected, so I pass the username & password to the request.
It loads the main HTML page fine, however, even after passing the username & password, any other pages that the main HTML page loads (i.e css and js) are all returning 401.
Is there a way around that?
Thanks,
Tee

This is one of the use cases for ASIWebPageRequest.
If you want something lower level, you'll have to create an NSURLCredential instance and an NSURLProtectionSpace and save them to the credential store. At this point, the UIWebView should use the credential for all requests that match the protection space (which basically represents your site).
Here's some sample code, taken from here, which is not really where I'd expect to find it.
NSURLCredential *credential = [[NSURLCredential alloc]
initWithUser: #"userName"
password: #"password"
persistence: NSURLCredentialPersistenceForSession];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost: #"www.mydomain.com"
port: 80
protocol: #"http"
realm: #"mydomain.com"
authenticationMethod: NSURLAuthenticationMethodDefault];
[[NSURLCredentialStorage sharedCredentialStorage]
setDefaultCredential: credential
forProtectionSpace: protectionSpace];
[credential release];
[protectionSpace release];

i think you need to pass the username and password in the css and js includes also...

Related

Programmatically logging in to website with saved username and password

One of the functions of an app I am making involves logging the user into our Campus Portal website. For ease of use, the user may enter a username and password into the app once, and it will be saved for all future log-ins. When the user clicks a button, the information will automatically be sent to log in, and the website will be displayed in a UIWebView.
Let us say that the username and password are each stored in an NSString. How can I use this data to log in to this website programmatically and display the page it in a UIWebView?
I know little about posting and forms, so any help is appreciated.
Would something like this Stackoverflow answer help?
Here's the shell of my code for this
- (IBAction)btnGo:(id)sender {
username = usernameField.text;
password = passwordField.text;
if (saveSwitch.isOn) {
//Save data if the user wants
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
usernameSaved = username;
passwordSaved = password;
[appDelegate.listOfPortalCredentials replaceObjectAtIndex:0 withObject:usernameSaved];
[appDelegate.listOfPortalCredentials replaceObjectAtIndex:1 withObject:passwordSaved];
}
//Insert username and password to web form, log in to portal
//This is where I need help
}
Edit:
Thanks to Karthik I was able to find the HTTP Post using Firebug. It gave me:
appName=ridgefield&portalUrl=portal%2Fridgefield.jsp%3F%26rID%3D0.18423783694092&username=<username>&password=<password>&B1=Log+In&url=portal%2Fmain.xsl%3FrID%3D0.6845596700302482&lang=en
where and represent the real username and password. I believe this is what I need. Using this, how can I display the logged-in page in a UIWebView? Do I just need to load the above URL in the UIWebView?
Use UIWebView, and do a http POST to https://ic.ridgefield.org/campus/verify.jsp with username and password.
To understand how it works, install Firebug on Firefox browser and go to 'Net' tab on firebug, and then open your website and enter some username/password.
You should mimic that action using code. (I always get invalid username or password response from server, coz i dont have an account and i try some random ones, and since there is no signup on the site, there is no way for me to verify this)
UIWebView* webView = [[UIWebView alloc] initWithFrame:self.view.frame];
[self.view addSubview:webView];
NSString* username = #"";
NSString* password = #"";
NSURL* url = [NSURL URLWithString:#"https://ic.ridgefield.org/campus/verify.jsp"];
NSString* body = [NSString stringWithFormat:#"appName=ridgefield&username=%#&password=%#", username, password];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:30];
request.HTTPMethod = #"POST";
request.HTTPBody = [body dataUsingEncoding:NSStringEncodingConversionAllowLossy];
[webView loadRequest:request];

Cocoa interface to MacOS X Keychain

I've got a bit of Mac code that needs to store, access and update passwords in order to connect users with a web API. The right place to put this information should be the Mac Keychain, but there doesn't seem to be a cocoa interface (see this answer) -- is this still correct?
I've looked at Apple's Keychain documentation, and the API seems incredibly clunky. I can store to it and retrieve records, but anything more complex seems to require a lot of thought as to what might go wrong (see this list of error codes).
Is there a better interface to the Mac keychain, aside from slogging through the C code? The closest I've come is EMKeychain but it seems like it needs a bit of work (e.g. no error handling code aside from spitting to the console).
You should take a look at SSKeychain. Works great, awesome code.
Too late answer but would be good for future help. Below is what I did to save the password in Keychain of Mac
#pragma -mark Password save in Keychain
-(NSURLProtectionSpace *)createProtectionSpaceForBasicAuthentication{
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:#"http://yourURLHere"
port:1804 //add Your port here
protocol:#"http" //can be pass as nil
realm:nil
authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
return protectionSpace;
}
-(void )createCredentialForUsername:(NSString *)username Password:(NSString *)password{
NSURLCredential *credentialObject;
credentialObject = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistencePermanent];
[[NSURLCredentialStorage sharedCredentialStorage] setCredential:credentialObject forProtectionSpace:[self createProtectionSpaceForBasicAuthentication]];
}
For saving password
- (IBAction)saveButtonClicked:(id)sender {
[self createCredentialForUsername:#"User_Name" Password:#"Your_Pass"];
}
for fetching the password
NSURLCredential *credential;
NSDictionary *credentials;
credentials = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:[self createProtectionSpaceForBasicAuthentication]];
credential = [credentials.objectEnumerator nextObject];
NSLog(#"Username: %# and password %#", credential.user, credential.password);
When we run the app to fetch password, we will get user action prompt for keychain access.
Late to the party, as always, but I can recommend UICKeyChainStore.

Is it possible to load an SSL-encrypted website with a self-signed security certificate in an iPhone application?

This is a question that seems to be floating around all over the place, but so far I haven't been able to find a definite answer.
I'm tasked with writing an iPhone application for my company. My company has an SSL-encrypted website with a self-signed certificate. This website is used very frequently, and my employer would like for the site to be easily accessible on an iPhone. My app's function is to provide a method of storing the credentials used to access the site, and when the user opens the app, it automatically sends the stored credentials to the site and skips the login dialog to go straight into the site.
I've set up my app to use a UIWebView and load a request for the site. I've set up the usual authentication methods, such as:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {...}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {...}
When my app reaches the part of the code where I tell the UIWebView to load the NSURLRequest, it doesn't encounter any of my authentication methods. It jumps straight to didFailLoadWithError and the error tells me that I'm connecting to a site that may only be pretending to be my site because it has a self-signed certificate.
How do I get around this? I've found a few answers involving the NSURLConnection methods, but those methods don't appear to be called before I reach the error and am forced to stop loading the page. I've also found some answers involving overriding "undocumented" methods, but when I try implementing that solution my web view never reaches either "didFailLoadWithError" or "didFinishLoad" and never displays any content.
The UIWebKit delegate doesn't forward through any of the NSURLConnection delegate methods to your app. One way to get around this would be to load the page using NSURLConnection and then push it into the UIWebView using -loadData:MIMEType:textEncodingName:baseURL:. Once you've done that you've verified the first page, which (as long as your site doesn't have links off of it), should stay safe. So, how do we verify a self-signed certificate?
I had to solve this with an OSX App a little earlier this year and, once I figured out what I was doing, it was pretty straightforward, assuming you have a similar setup. The solution I propose here actually verifies the server certificate (although in my case I was using a private CA, so I added the CA certificate to the trust root, instead of the server certificate, it should work just as well with that).
You'll need to add tests for NSURLAuthenticationMethodServerTrust to both the -connection:canAuthenticateAgainstProtectionSpace: and -connection:didReceiveAuthenticationChallenge: methods so that you can both request interest in and process the Security challenge.
Hope this helps.
-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
// ... implement any other authentication here, such as your client side certificates or user name/password
if ([[protectionSpace authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust])
return YES;
return NO;
}
-(void)connection:(NSURLConnection *)aConnection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
// implement your client side auth here for NSURLAuthenticationMethodHTTPDigest or basic or your client-side cert
if ([[protectionSpace authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
// install our private certificates or CAs
NSString *myCAString = #"<string containing your CA cert>";
SecCertificateRef certRef = SecCertificateCreateWithData ( NULL, (CFDataRef)[NSData dataFromBase64String: myCAString]);
NSArray *anchorArray = [NSArray arrayWithObject: (NSObject*)certRef];
SecTrustSetAnchorCertificates( challenge.protectionSpace.serverTrust, (CFArrayRef) anchorArray);
SecTrustResultType trustResultType;
OSStatus err=SecTrustEvaluate(challenge.protectionSpace.serverTrust, &trustResultType);
if ((!err) && (trustResultType == kSecTrustResultProceed || trustResultType == kSecTrustResultConfirm || trustResultType == kSecTrustResultUnspecified)) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
} else {
CFArrayRef certChain=NULL;
CSSM_TP_APPLE_EVIDENCE_INFO *statusChain;
SecTrustGetResult( challenge.protectionSpace.serverTrust, &trustResultType, &certChain, &statusChain);
[challenge.sender cancelAuthenticationChallenge:challenge];
}
} else {
// Cancel if we don't know what it is about... this is supposed to be secure
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}
I have managed to do what you desire by using RestKit framework, so yes, I can confirm it can be done. Go see in the RestKit sources how they do it, or just use RestKit. This says how to use SSL with RestKit. In my case, there was one catch - the self signed certificate had to contain the certain extensions, otherwise I always got the kSecTrustResultRecoverableTrustFailure. Go see here, the answer is voted -1 but it actually fixed the problem for me.

UIWebView, enter a password

I want to enter a password in a webpage I load in an UIWebView. The password is a string, and I want to enter the password and hit a button in the page, automatically when the View loads.
Any help?
If you're using a UIWebView, I assume you're loading some web service into that WebView. Why not build your login system into the web service? You could even just use HTTP Basic Auth.
If you're managing the passwords within your cocoa app, then I would probably implement a UIAlertView with a UITextField asking for the password. When the user presses Login you can validate the password and then load your web service in your UIWebView.
If you're managing the passwords in a database on the website then you'd build an html/js/php/mysql login form and load it in the UIWebView first.
Update
I'd go with the first option and display your own login form using cocoa controls like the UIAlertView with 2 UITextFields. You can then save the username and password using the keychain services and submit them every time the users launches the app or the web session expires.
If you want to try this method I'd suggest having a look at the ASIHTTPRequest framework. In particular ASIFormDataRequest which let's you easily send a form with the correct fields.
- (void)login
{
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
// Change these values to the ones in NSUserDefaults and the KeyChain
[request setPostValue:#"Joe" forKey:#"username"];
[request setPostValue:#"Secret" forKey:#"password"];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data.
NSData *responseData = [request responseData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
Just an idea : you could try to add some javascript to the page before displaying it in the webview and use the ONLOAD event to fill the password field and post the form.
To do so you have 2 solutions :
Use the stringByEvaluatingJavaScriptFromString instance method of UIWebView , you could evaluate your javascript in the webViewDidFinishLoad delegate method for example. You will find an example here : http://iphoneincubator.com/blog/windows-views/how-to-inject-javascript-functions-into-a-uiwebview
Or if for some reason you can't use this solution in your context you can always download the HTML using NSURLConnection, and then edit the string to insert your javascript. Then load the WebView using loadHTMLString:baseURL:

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