For some background info, the webpage I'm trying to display is a web app currently being hosted on AWS's EC2. The backend is Python w/ Flask and the frontend is just simple HTML/CSS. The URL has HTTP, as it isn't secured with HTTPS yet. When the url for this webpage is opened, the first thing the browser asks is for login credentials (the browser asks, not the website). This page does load in mobile Safari on my iPhone, and Safari does successfully ask for the credentials. If I enter them in correctly, it will correctly load the page.
So I've tried both Allow Arbitrary Loads under App Transport Security Settings as well as a customized Exception Domain with the following keys:
App Transport Security Settings Dictionary
Exception Domains Dictionary
my website URL Dictionary
NSIncludesSubdomains Boolean (YES)
NSExceptionAllowsInsecureHTTPLoads Boolean (YES)
NSThirdPartyExceptionAllowsInsecureHTTPLoads Boolean (YES)
NSExceptionMinimumTLSVersion String (TLSv1.0)
NSExceptionRequiresForwardSecrecy Boolean (YES)
However, whenever I launch the app on the simulator all I'm getting back is a white screen (can post screenshot if needed).
Here's my code in ViewController.swift:
import UIKit
class ViewController: UIViewController {
#IBOutlet var WebView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "My URL inserted here")
let request = NSURLRequest(URL: url!)
WebView.loadRequest(request)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
If I use Allow Arbitrary Loads, when I look in the output box, it does not say "App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file." When I configure the Exception Domain correctly (with Allow Arbitrary Loads removed) it won't give me the message either. Only sometimes when I change around the settings using Exception Domain (again, with Allow Arbitrary Loads removed) do I get this output.
I'm starting to think the issue goes beyond security, and any advice or steps I could take to try and fix this issue would be much appreciated, thanks!
The white screen is a bit odd, assuming that a 401 would result in a standard error page, but maybe the server set up a white page for this.
My guess is that setting username and password directly in the URL doesn't work, you shouldn't do that anyways, but instead rely on WKWebView's webView:didReceiveAuthenticationChallenge: delegate method.
Here's some sample code hopefully working/helping:
#import "ViewController.h"
#import WebKit;
#interface ViewController () <WKNavigationDelegate>
#property (nonatomic, strong) WKWebView *webView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:[WKWebViewConfiguration new]];
self.webView.navigationDelegate = self;
[self.view addSubview:self.webView];
[self.webView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[_webView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(_webView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[_webView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(_webView)]];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSURL *target = [NSURL URLWithString:#"http://yourhost.com/possiblePage.html"];
NSURLRequest *request = [NSURLRequest requestWithURL:target];
[self.webView loadRequest:request];
}
- (void)webView:(WKWebView *)webView
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
NSURLCredential *creds = [[NSURLCredential alloc] initWithUser:#"username"
password:#"password"
persistence:NSURLCredentialPersistenceForSession];
completionHandler(NSURLSessionAuthChallengeUseCredential, creds);
}
#end
This is basically the implementation file of a simple ViewController (like from the single view template of XCode). It also shows you how you can add a WKWebView. Definitely make sure to check out all the delegate methods and such so you know what the thing can do for you.
Obviously, password and username have to be set somehow, I guess you can use a simple alert popup to have the user enter this info (this would be similar to Safari in principle). For the first test you can just hardcode it. Also note I set a sample subpage there, just use the exact same URL you would usually use on a desktop browser. Oh, and since the server doesn't have SSL, you need to allow arbitrary loads.
Edit:
RPM gave a good related comment below (thanks) that I had not originally thought about. The method may (actually will very likely) be called multiple times. This ultimately also depends on the website you load. RPM's explanation for why a site may appear plain white is spot on.
In any way, the webView:didReceiveAuthenticationChallenge:completionHandler: method above is just a simple example assuming you know the PW and username. Generally it will be more complex and you shouldn't just open an input dialog every time it is called for the user to enter credentials. As a matter of fact, the provided challenge offers ways to set a specific call to this delegate method into relation to previous calls. For example, it has a proposedCredential property that may already have been set. (Whether that's the case for loading multiple resources I don't know on the top of my head, just try that out.) Also, check its previousFailureCount, etc. A lot of this may depend on the site you load and what it needs to get.
Related
I have an app which has a UIWebView inside of it with a loaded website. This website has a chart in it which is periodicly updated with data from remote server via websockets (socket.io).
Im new to websockets technology but Im trying to somehow intercept the chart data that the website is receiving from server via it.
Till now I have managed to catch http requests sent by the website of such address format: “http://website-address/socket.io/?auth_token=...”
I have the socket.io library for iOS but don’t know how to use it to somehow spoof the website connection and acquire the data downloaded by the website. Can anyone help? Is it even possible?
Switch to WKWebView if you can. Using javascript bridge is much easier there. That said, with UIWebView, you'd need to inject a script that adds a handler for events received by the socket that you are trying to listen to. You can either create an io variable by yourself but apparently the server needs auth token. If you cannot create an auth token, you can only do this if you have access to the io variable created by the website.
Then for adding a handler, you'll need to know what the event name is, that delivers the chart data. You can snoop around the website and see if you can find that. If you cannot all bets are off. Once we register a handler and get the data, we need to pass this back to your native code. This is where WKWebView would keep it clean by letting you add message handlers that can deliver messages from js to native code. For UIWebView you'll have to create a custom url scheme and spoof a navigation request to pass the data. Lets assume your custom url scheme is 'myApp'. Then the script you'd need to inject would be:
<script>
/* if you can access/create the auth token
var socket = io('http://website-address/socket.io/?auth_token=');
*/
var socket = getioReferenceCreatedByWebsite();
socket.on('<eventName>',function(){
window.location = 'myApp://<data>';
};
</script>
In your native code:
...
webView.delegate = self;
[webView stringByEvaluatingJavaScriptFromString:#"<theAboveJSAsAString>"];
....
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
if(request.URL.scheme == #"myApp"){
NSString *data = request.URL.path;
//handle the data
return NO;
}
return YES;
}
In regards to Santhosh R answer. I had the problem he mentioned where I could not get a reference to the websocket object as it was caught up in a closure.
I solved this by adding in a preload script which wraps the native Websocket object to store any instantiated websocket objects in an array and then return the newly created websocket object.
Here is the code.
in your WebView element add in a preload attribute.
<webview id="myWebview" src="http://exmple.com" preload="./interceptor.js"></webview>
and then in inteceptor.js
window.NativeWebsocket = WebSocket;
window.WebSocket = function(url, protocols){
window.interceptedWebsockets = [];
var ws = new NativeWebsocket(url, protocols);
interceptedWebsockets.push(ws);
return ws;
}
Then, inside your WebView context you can an access array of instantiated websocket objects using window.interceptedWebsockets
Our customer requested us to do a switch from WebView to WKWebView recently. Their app uses native login, which is done by a 2 POST calls to their backend, returning various authorization cookies that were later on used in every single HTTP/AJAX call throughout the whole app.
With the WebView, it all worked like a charm without a need to implement a single line of custom code. User logged in, cookies were stored to cookie storage by default, and WebView always took them from there and used them, since the HTTPCookieStorage was shared between NSURLSession and WebView.
It it a whole new story with WKWebView. Once we switched WebView to WKWebView, we saw that the authorization was not working. It was due to losing some cookies in the WKWebView. We store the cookies from the NSURLSession response now and append them to the WKWebView manually, by adding "Cookie" header to the HTTP requests.
We were able to get the authorization for HTTP calls work this way, but now we are seeing a new problem. Somehow, all the AJAX calls done in the WKWebView lose the authorization cookies.
Do you please know if there is any way to somehow have the authorization cookies appear in the AJAX calls too? Injecting javascript with
javascriptCookieString = #"document.cookie = 'testCookie=testValue';";
[self.webView evaluateJavaScript:javascriptCookieString completionHandler:nil];
did not work and it seems like there is no control whatsoever over Javascript calls, so I cannot alter the requests before they are being executed. Thank you.
I found that the following snippet did the trick for us. We had the same problem.
// add session cookie to ajax calls
WKUserContentController* userContentController =
WKUserContentController.new;
WKUserScript * cookieScript =
[[WKUserScript alloc]
initWithSource: [[User sharedInstance] getJavscriptCookieString]
injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:cookieScript];
WKWebViewConfiguration *webViewConfiguration = [[WKWebViewConfiguration alloc] init];
webViewConfiguration.userContentController = userContentController;
webViewConfiguration.preferences.javaScriptCanOpenWindowsAutomatically = true;
_wk = [[WKWebView alloc] initWithFrame:self.view.frame configuration:webViewConfiguration];
The cookie string needs to be formated correct in order to be accepted.
-(NSString*) getJavscriptCookieString {
return [NSString stringWithFormat: #"document.cookie = '%#=%#'", [self getSessionName], [self getSessionValue]];
}
Hope this can be of some help.
See also:
Can I set the cookies to be used by a WKWebView?
Apple's documentation is clear on using SLComposeViewController to provide sharing capabilites with other social networks such as Twitter and Facebook.
Typical code will use isAvailableForServiceType to verify if a particular service is available and then add a completion handler to the view controller where an SLComposeViewControllerResult can be checked for either SLComposeViewControllerResultCancelled or SLComposeViewControllerResultDone, which check if the user tapped on the 'Post' button or on the 'Cancel' button after the sharing view has been displayed.
The issue here is that if you use SLComposeViewControllerResultDone to validate that the user made the request, you don't actually check if it was successful such as when the user has limited or no connectivity.
I have tried with one of my apps to test this and have noticed that the SLComposeViewControllerResultDone constant is still valid even if airplane mode is turned on such that a request is not possible. What this means is that the user fills out the sharing view fields and taps on 'Post' and my success code executes even though I should really be checking to make sure that the post was indeed successful.
Currently, I figure that the best option is to check for an Internet connection using the standard Reachability options (as recommended here) and disable the sharing button if a connection is not available, but I'm not sure if this is the best solution as it doesn't account for a limited connection where the user can tap on 'Post' but the actual request is unsuccessful.
My question is what is the best method of detecting if a sharing request has successfully completed?
then you need to make sure that you do not write below line in didSelectPost
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
and once you get success or fail based on that in the your request handler you can write above line, so your didSelectPost should be like :
- (void)didSelectPost {
NSExtensionItem *inputItem = self.extensionContext.inputItems.firstObject;
NSItemProvider *attachment = inputItem.attachments.firstObject;
if ([attachment hasItemConformingToTypeIdentifier:#"public.url"])
{
//NSString *strLink = [attachement loadItemForTypeIdentifier:#"public.url" options:nil completionHandler:nil];
[attachment loadItemForTypeIdentifier: #"public.url"
options: nil
// make your request here
}];
}
}
I am trying to access a secure for local network url through UIWebView. When I access it through safari, i get an authentication challenge but the same does not appear in my UIWebView in the application. How can I make it appear?
E.g. http://292.168.1.54/TestWeb/Test.pdf
This url working in safari browser but the same url does not appear in my UIWebView.
Any pointers, sample code or links will be very helpful. Thanks a lot.
There are two ways to get to the authentication (in your case probably basic auth) challenge.
-[UIWebViewDelegate webView:shouldStartLoadWithRequest:navigationType:] will give you the request. Now you just start a second request to the same URL and use [NSURLConnectionDelegate connection:willSendRequestForAuthenticationChallenge:] to get the challenge. Then you present a dialog and ask the user for credentials. Save the credentials in NSURLCredentialStorage and then reload the page.
Create a subclass of NSURLProtocol that handles http and https. Similar to this answer and get the authentication challenge there.
I hope this code will help you.
(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.webView.delegate = self;
NSURL *url = [NSURL URLWithString:#"http://skinC.com/abc/"];// here you can write your url that you want to open
NSURLRequest *requestURL = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:requestURL];
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[self setNeedsStatusBarAppearanceUpdate];}
I'm very new to iOS development so please be gentle.
I understand that webViewDidFinishLoad will fire for each <iframe> plus the original html page.
I'm trying to find the URL of the request that resulted in that webViewDidFinishLoad. As far as I can tell, I can only access webView.request.mainDocumentURL.
I find this perplexing. Inside the shouldStartLoadWithRequest method, which fires also for each <iframe> and the original html request, you can access the URL in question, be it the iframe or the parent page.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"Parent page request: %#", request.mainDocumentURL);
NSLog(#"Actual URL request: %#", [request URL]); // This returns what I want.
return YES;
}
That first log statement will output the url.
However, inside webViewDidFinishLoad
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSURLRequest* request = [webView request];
NSLog(#"Parent page request: %#", request.mainDocumentURL);
NSLog(#"Request outputs the parent, not the iframe: %#", [request URL]);
// how do I access the <iframe> url?
}
Help me StackOverflow. My google-fu is weak and your wisdom is strong.
EDIT: I should be clear on the goal of this. I want to Do Stuff™ when the load event of the parent page fires, not for iframes. I want this to happen as soon as possible, so I don't want to wait until everything finishes.
Think of the UIWebView as your browser. The scope of the object you are interrogating is at the browser level, the iFrames are more granular than that. The URL that populates the iFrame is used to stream content for that section of the page, but for the purposes of the UIWebView, you only need to remember the main URL so the page can be managed (reloaded, etc...) as that URL clearly brings with it instructions as to how to manage the iFrame's content.
If you really need to those iFrame source URLS, simply extendeding UIWebView to hold an array of links (populate the array inside "shouldStartLoadWithRequest"). You can then do whatever you want with that list of links.
Good Luck.