I made an application that uses UIWebView to display a site that uses basic authentication. It stopped displaying the content since iOS3.2 (on both iPhone and iPad).
My approach for authenticating users are:
create a NSURLConnection
implement didReceiveAuthenticationChallenge to provide the credential
implement didReceiveResponse to receive the response and load the request in a UIWebView, i.e. [webView loadRequest:]
Ever since iOS3.2, the authentication was broken; UIWebView doesn't seem to check against the shared credential storage any more.
For iOS4 on iPhone, I managed to work around this issue by, stupidly, including the login informatino in the URL (i.e. https://username:password#www.somesite.com) to the initial load request passed to the webView.
For iOS3.2 on iPad, that isn't enough. The initial request is authenticated all right, but the subsequent calls still aren't authenticated.
Any suggestions?
Use setDefaultCredential:forProtectionSpace: on the shared NSURLCredentialStorage
the Webview still uses the default credential, it does not work when you use setCredential:forProtectionSpace.
Related
I have an application that may access authenticated content. I know that the webview can't handle authentication so I do some NSConnection magic to make it work (something similar to this)
The thing is that there is some content that can be accessible using this web view, but there is some other content that event after a sucessfull authentication, the web view is not able to load.
BUT.. if I enter the same url with mobile safari, enter the needed credentials and then I go back to my app, the WebView seems to load the content fine.
I tried reviewing the cookies before and after the auth in Safari is done using this code
[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
and the cookies are the same.
As far as I understand that code will retrieve the cookies my app generates and not the ones available in iOS, so apparently this is not the way to look for a hint...
Any ideas?
Recently, i've lurked for the same question over the internet, and the answer is "no" =(.
Objects of UIWebView class and Safari or other browsers live apart and are sandboxed.
Here is official position about cookies.
TO the best of my understanding, Cookies can not be sent with the first request from a Webview, but can be sent with subsequent requests to the same URL, if and only if, the first request was successful.
This causes problems with authentication services that require cookies to authenticate on the first request to the URL.
Possibly user credentials are stored via keychain api. Keychain is shared between apps, so stored login/pass in Safari can appear in your app UIWebView.Can you elaborate this as i also need this.
Maybe I'm missing something, but from Apple's documentation for NSHTTPCookieStorage, I can't help but wonder how this is safe to use.
Does this mean that cookie storage is shared across all apps on the iPhone? If my app makes an Http call that results in some cookies being saved, do all apps now have access to these cookies?
Methods like:
cookiesForURL: Returns all the
receiver's cookies that will be sent
to a specified URL.
make it look even more suspicious.
Can someone explain how this is OK, and what the class does?
Also, assuming my understanding is flawed and this is indeed sandboxed per-app, do calls made using NSURLRequest automatically save/retrieve cookies from this store or is it the developers responsibility to set request headers before dispatching the request?
Your application only has access to cookies within its own sandbox.
From http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/URLLoadingSystem/Concepts/URLOverview.html:
iPhone OS Note: Cookies are not shared by applications in iPhone OS.
I guess you're confused by the fact that you can access cookies from other domains/urls.
That's is technically true because your native app is "a browser" when you use UIWebView.
If you load www.siteA.com and www.siteB.com in your UIWebView, both domain's cookies are available to your objc code.
All apps, including mobile safari has it's own CookieJar and none of them can access the other one.
Is it possible to set a cookie in an iPhone Application that persists, so that later when the user is in Mobile Safari, that cookie can be sent to a webserver?
** Update 2017 **
A lot of changes to security mechanisms and cross-app communication were introduced to iOS in the recent years since this was first answered.
The below code no longer works on current iOS releases since Safari no longer accepts javascript:... in URLs and frameworks like NSURL catch these and return nil.
The one alternative that still works is to either host a website and have Safari open it or integrate such a HTML page in your app and run a small http server to host it on demand.
**iOS up to 6.x **
Since Apple has forced the sandboxing on all app store applications
there's currently no easy way to realize your request.
You could however open a special http://-URL from your application containing javascript to place a cookie:
NSString jsURL = #"javascript:function someFunction(){ /* your javascript code here */ } someFunction();void(0)";
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: jsURL]];
Using javascript in URLs has been used by different iPhone applications to cross communicate
with MobileSafari (for example instapaper).
Another option would be to include a static HTML page in your app or on your server and instruct MobileSafari to open it.
The page in turn could set the permanent cookie.
Hope this helps!
I believe this is made easy by using the ASIHTTPRequest Library. It encapsulates the use of the global cookie store.
http://allseeing-i.com/ASIHTTPRequest/How-to-use
You can make requests with this library which will accrue cookies, and then these cookies will affect other requests later.
I use this to great effect in accessing authenticated APIs within my iPhone app.
The documentation for NSCookieStorage suggests that it would be such a mechanism. But whether "all applications" really includes Mobile Safari or not, your experimentation will have to determine....
See also the general documentation for the URL Loading System.
I'm new at iPhone development, but wouldn't opening a UIWebView allow your server to set a cookie on the browser so then when the user visits the site with Safari the cookie would be readable to your web server? Perhaps a hidden or small UIWebView? I can't tell what the use case is from your question.
Launching a URL in Safari is easy enough in an iPhone application - UIApplication's openURL method.
However, I'm doing some OAuth stuff, and want to follow the pattern that pownce used to handle OAuth. But since OAuth wants me to explicitly set the Authorization header, I don't know how to proceed. I can set headers via NSMutableURLRequest... but how does that translate into creating an NSURL that my app can pass to the UIApplication method?
During server to server interactions the recommended method of passing OAuth parameters from the Consumer to the Service Provider is using the HTTP Authorization header. But when the user is redirected by the Consumer to the Service Provider OAuth actually specifies that an HTTP GET request should be used (see section 6.2.1, I can't link b/c I'm a new user and can only post one hyperlink -- very annoying StackOverflow). So there's no need to pass an NSURLRequest off to mobile safari, simply open the URL as you described.
If you're interested in seeing a code example of the full OAuth flow on the iPhone feel free to check out the sample FireEagle app I wrote. It was implemented in much the same way the Pownce app was.
From what I've seen, the usual way to implement OAuth support in an iPhone application is to present a modal sheet with a webview in it. This allows you to use an NSURLRequest with custom headers.
I'm working on an iPhone app that ideally uses OAuth to communicate with Twitter. I know a lot of people are doing the OAuth workflow inside of their apps using a UIWebView, but I don't agree with that and am going with the Pownce approach.
The problem is, Twitter has this whole scheme for working with desktop apps, using a pin number. When I register my app with Twitter, they have a web form asking me if I'm a desktop or web client. If I choose desktop client, when I try to have the user authorize, I can set the oauth_callback parameter but Twitter will ignore it after authorization and show a pin number. If, on twitter's form, I specify that I'm a web client, it requires me to enter a URL to redirect to after authorization. And, since I'm using an iPhone app-specific url scheme, their web form fails on validation as it only seems to accepts URLs conforming to the HTTP protocol.
So, it seems like I'm stuck - I can't say "desktop" because I don't want to bother with a pin, and I can't say "web" or I can't use an iPhone app URL. Any solution to this?
From your question:
I know a lot of people are doing the OAuth workflow inside of their apps using a UIWebView, but I don't agree with that and am going with the Pownce approach.
The Pownce article suggests that quitting your application and opening Mobile Safari to perform the authentication step is problematic, and that they started receiving bad reviews from users for doing it that way. They also experienced a failure rate of around 40%.
Pownce's solution is to use a UIWebView within your application instead, so I have a feeling you may have misinterpreted their recommendations. That being said, they do label this as a "naive" solution and go on to suggest a bunch of theoretical "ideal" solutions.
Another point you might not realise is that desktop applications (using the "out of band" / pin number method) and web applications need to open the Twitter site in either an embedded or external browser.
So you've got two choices on the iPhone:
Open up twitter.com in a UIWebView, specifying no oauth_callback parameter or oauth_callback=oob to start the pin-based out-of-band flow. The user then needs to copy the pin using the iPhone's copy-paste functionality, manually close the UIWebView, and paste the pin into your application. The pin can then be used converted to an access token.
Do it how everyone else is doing it (UIWebView + custom-uri://foo.bar in the callback parameter).
For obvious reasons, the first option is pretty crap and really only useful on platforms where Twitter is unable to redirect to a custom URI.
A simple solution may be to create an HTTP page that always sends a 301 redirect to your custom URL scheme and then provide that HTTP URL to the twitter web API.
Aside from that, Nathan's answer is very complete.
Here's how I do it: tell Twitter you're a web app, and make up any old HTTP:// URL to satisfy Twitter during registration of your oauth client.
Then in your app, pass the URL you want to in the callback parameter. Twitter (in my experience) uses the one you give it.
You could use an intermediate website for the authentication. Your app creates a unique id (hardware based?) and stores it. It then records it has sent the user for authentication and sends the unique id to your website. It then redirects the user to your website. Your website then sends the user to Twitter using oAuth. The user returns to your website and you mark the unique id as authenticated and store the authentication information. The user restarts the app on the iPhone, it reads it has sent the user for authentication and contacts your website with the unique id - and reads in the authentication information.
Long winded and needs another website, but it should work.