How can I get UIWebView to open Facebook login page in response to the OAuth request on iOS 5 and iOS 6?

14,294

Solution 1

just use : this code

if (![FBSDKAccessToken currentAccessToken]) 
{   
    FBSDKLoginManager *manager = [[FBSDKLoginManager alloc]init];
    manager.loginBehavior = FBSDKLoginBehaviorWeb;
    [manager logInWithReadPermissions:@[@"public_profile", @"email", @"user_friends"] handler:
     ^(FBSDKLoginManagerLoginResult *result, NSError *error) {

         NSLog(@"result.token: %@",result.token.tokenString);  
         NSLog(@"%@",result.token.userID);   
         NSLog(@"%hhd",result.isCancelled);     
     }];
}

// here manager.loginBehavior = FBSDKLoginBehaviorWeb; is all you need to open facebook in UIWebview

Solution 2

Did it!

It kinda of a hack, but the js facebook sdk login on UiWebView at iOS 6 finally works.

How it could be done? It is a pure JS + Facebook JS SDK + UIWebView Delegate handling functions solution.

JS - First step)

a login button (to connect with facebook) calls this function example, that will trigger Face JS login/oauth dialogs:

function loginWithFacebookClick(){

FB.login(function(response){
    //normal browsers callback 
});

}

JS - Second step)

add a authResponseChange listener every time user loads the webpage ( after FB.init() ) to catch user's connected status:

FB.Event.subscribe('auth.authResponse.Change', function(response){
//UIWebView login 'callback' handler
var auth = response.authResponse;
if(auth.status == 'connected'){
    //user is connected with facebook! just log him at your webapp
}
});

AND with app's UIWebView delegate functions you can handler facebook oauth responses

Objective C - Third step)

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *url = [[request URL] absoluteString];

//when user status == connected (has a access_token at facebook oauth response)
if([url hasPrefix:@"https://m.facebook.com/dialog/oauth"] && [url rangeOfString:@"access_token="].location != NSNotFound)
{
    [self backToLastPage];
    return NO;
}

return YES;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{

NSString *url = [[webView.request URL] absoluteString];

if([url hasPrefix:@"https://m.facebook.com/dialog/oauth"])
{
    NSString *bodyHTML = [webView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"];

    //Facebook oauth response dead end: is a blank body and a head with a script that does 
    //nothing. But if you got back to your last page, the handler of authResponseChange
    //will catch a connected status if user did his login and auth app
    if([bodyHTML isEqualToString:@""])
    {
        [self backToLastPage];
    }
}

}

So, when 'redirect' user to the last loaded page, the second step is going to handler user action at facebook login dialogs.

If I got too fast with this answer, please ask me! Hope it helps.

Solution 3

In case anyone is googling, here's what worked for me:

-(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)inType {
    if ([request.URL.absoluteString containsString:@"m.facebook.com"]) {
        if ([request.URL.absoluteString rangeOfString:@"back"].location == 0) {
            [self.popUp removeFromSuperview];
            self.popUp = nil;
            return NO;
        }
        if (self.popUp) {
            return YES;
        }

        UIWebView *wv = [self popUpWebView];
        [wv loadRequest:request];
        return NO;
    }
    return YES;
}

- (UIWebView *) popUpWebView {
    toolbar height
    UIWebView *webView = [[UIWebView alloc]
            initWithFrame:CGRectMake(0, 0, (float)self.view.bounds.size.width,
                    (float)self.view.bounds.size.height)];
    webView.scalesPageToFit = YES;
    webView.delegate = self;
    // Add to windows array and make active window
    self.popUp = webView;
    [self.view addSubview:webView];
    return webView;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {

    if (self.popUp) {
        NSError *error = nil;
        NSString *jsFromFile = @"window.close=function(){window.location.assign('back://' + window.location);};";
        __unused NSString *jsOverrides = [webView
                stringByEvaluatingJavaScriptFromString:jsFromFile];

        JSContext *openerContext = [self.webView
                valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        JSContext *popupContext = [webView
                valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        popupContext[@"window"][@"opener"] = openerContext[@"window"];
    }


  //this is the secret sauce  
  if (webView == self.popUp
            && [webView.request.URL.absoluteString containsString:@"m.facebook.com"]
            && [[webView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"] isEqualToString:@""]) {
        [webView stringByEvaluatingJavaScriptFromString:@"eval(document.getElementsByTagName('script')[0].text)"];
    }
}

I snagged a bunch of this implementation from here.

Depending on your web implementation, there will likely be one extra step. The Facebook script actually executes a window.close() then a window.open() then a window.close(). For me this was causing problems because on the web side, after this login is complete, my window (i.e. for the webView that I want the user to log in to) was getting a window.close() call, coming from the Facebook SDK. I'm assuming this is because the Facebook SDK expects that window.open() call to open a new window that it will close.

Since we didn't override the functionality of window.open(), calling window.open() won't do anything, and the Facebook SDK will attempt to close your window. This could cause all kind of problems, but for me since I'm using Parse, window.localStorage was set to null so I was getting all kinds of errors.

If something like this is happening for you, you have two options to fix it:

  1. If you have control of the web code, and your down for a small hack, throw this in window.close=function(){}
  2. If you don't have control of the web code, you can either add an override to window.close for the main webView like we did for the popUp webView, or override the window.open function to open another popUp (which is described in more detail here)
Share:
14,294
Admin
Author by

Admin

Updated on June 04, 2022

Comments

  • Admin
    Admin almost 2 years

    We have:
    (1) Facebook API-based web application with Facebook OAuth functionality (“the FB web app”)
    (2) UIWebView-based browser on iPad (“the Browser”)

    Our objective is to open the Facebook Login page to sign in to the FB web app (1) inside the UIWebView-based Browser (2) on iPad.

    There is a somewhat similar issue here:
    http://facebook.stackoverflow.com/questions/11337285/no-longer-able-to-login-to-ios-app-via-oauth-the-page-requested-was-not-found

    However, the issue of that question happens after the user enters login and password into the Facebook form. Our problem is that we cannot get the Facebook login form displayed in the first place. Changing the app type to from “Web” to “Native/Desktop”, as suggested in that question, did not help.

    Steps:
    1. Open our web page (simple HTML page) with this UIWebView Browser
    2. Click on “FB web app” launch button on this page
    3. OnClick JavaScript tries to initiate OAuth, which should open the login screen of Facebook to sign in to the FB web app

    Current outcome (issue):
    On iOS 5.+ and iOS 6.+ devices
    - Our web page stays unchanged
    - Facebook login page is NOT shown (our web page is still displayed)
    On iOS 4.3 (works as expected):
    - the Facebook login page is opened in the same UIWebView object of the Browser (replaces our web page)

    Expected outcome:
    - Facebook login page is displayed, and the user can enter Facebook login & password
    - Works on iOS 5.+ and iOS 6.+ if launched in Safari browser on iPad. Facebook login page is opened in a separate tab (in contrast, there are no separate tabs in UIWebView)

    Question: How can I get UIWebView to open Facebook login page in response to the OAuth request on iOS 5+ and iOS 6+?

    More technical details:

    We log different NSURLRequest fields from within

    -(BOOL)webView(UIWebView*)webView shouldStartLoadWithRequest(NSURLREquest*)request navigationType:…   
    

    And we notice some difference in logs for “correct” and “incorrect” behaviors. Here how execution flows look for me:

    Firstly, I press “FB Web App” launch button to initiate OAuth, then some cases go

    iOS 4.3, “correct”
    request to www.facebook.com/dialog/oauth?...
    request to fbwebapp.com
    request to m.facebook.com/login.php?....
    --here facebook login appears

    iOS 5.0, “incorrect1”
    request to www.facebook.com/dialog/oauth?...
    request to fbwebapp.com
    request to m.facebook.com/login.php?...

    Then it may be
    --a lot of m.facebook.com/login.php?...with next… in parameters
    followed by sqlite error
    --right now I see “Sorry, something went wrong” page from facebook (it’s a first time at all I encounter it)

    iOS 6.0 “incorrect2”
    request to www.facebook.com/dialog/oauth?...
    request to fbwebapp.com

    -(void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error is invoked with error code -999

    You can see that behavior definitely depends on iOS version. But common case is that error happens on the step of obtaining m.facebook.com/login.php.. URL. But that’s all that we can detect.

    We’re banging our heads against that wall for the whole day looking for solutions. Hopelessly.
    Can you help us get the Facebook Login page opened in the UIWebView in response to OAuth?