SafariViewController: How to grab OAuth token from URL?

前端 未结 2 1740
时光说笑
时光说笑 2020-12-08 05:01

Trying to use Facebook OAuth with the SafariViewController. First I open the authURL with SafariViewController, which if the user is logged in to Facebook on Safari, will re

相关标签:
2条回答
  • 2020-12-08 05:23

    Figured it out. Some of the methods were pre iOS 9 and now deprecated. I also had the application function in the ViewController I created when it should have been defined in the AppDelagate.swift. For example

    Added at end of AppDelegate.swift

    func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
    
            print("app: \(app)")
            // print OAuth response URL
            print("url: \(url)")
            print("options: \(options)")
    
            if let sourceApplication = options["UIApplicationOpenURLOptionsSourceApplicationKey"] {
                if (String(sourceApplication) == "com.testApp.Incognito") {
                    NSNotificationCenter.defaultCenter().postNotificationName(kSafariViewControllerCloseNotification, object: url)
                    return true
                }
            }
            return true
        }
    

    ViewController.swift

    import SafariServices
    
    let kSafariViewControllerCloseNotification = "kSafariViewControllerCloseNotification"
    
    // facebook OAuth URL
    let authURL = NSURL(string: "https://www.facebook.com/dialog/oauth?client_id=3627644767&redirect_uri=https://www.facebook.com/connect/login_success.html&scope=basic_info,email,public_profile,user_about_me,user_activities,user_birthday,user_education_history,user_friends,user_interests,user_likes,user_location,user_photos,user_relationship_details&response_type=token")
    
    class ViewController: UIViewController, SFSafariViewControllerDelegate {
    
        var safariVC: SFSafariViewController?
        @IBOutlet weak var loginButton: UIButton!
    
        @IBAction func loginButtonTapped(sender: AnyObject) {
            safariVC = SFSafariViewController(URL: authURL!)
            safariVC!.delegate = self
            self.presentViewController(safariVC!, animated: true, completion: nil)
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.safariLogin(_:)), name: kSafariViewControllerCloseNotification, object: nil)
        }
    
        func safariLogin(notification: NSNotification) {
            // get the url from the auth callback
            let url = notification.object as! NSURL
            // Finally dismiss the Safari View Controller with:
            self.safariVC!.dismissViewControllerAnimated(true, completion: nil)
        } 
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
        func safariViewControllerDidFinish(controller: SFSafariViewController) {
            controller.dismissViewControllerAnimated(true) { () -> Void in
                print("You just dismissed the login view.")
            }
        }
    
        func safariViewController(controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
            print("didLoadSuccessfully: \(didLoadSuccessfully)")
    
        }
    }
    
    0 讨论(0)
  • 2020-12-08 05:42

    iOS 12+

    iOS 12 Beta already deprecates SFAuthenticationSession (see below) in favor of ASWebAuthenticationSession. It looks like it is used exactly the same way but requires the new AuthenticationServices framework.

    iOS 11

    iOS 11 introduced SFAuthenticationSession which is so much easier to handle. Given its nature this beta API may still change but there already are a couple of examples (1, 2) on the internet. First, you need a completion handler that is called with the result of the authentication request:

    let completion : SFAuthenticationSession.CompletionHandler = { (callBack:URL?, error:Error?) in
        guard error == nil, let successURL = callBack else {
            return
        }
    
        let oauthToken = NSURLComponents(string: (successURL.absoluteString))?.queryItems?.filter({$0.name == "oauth_token"}).first
    
        // Do what you have to do...
    }
    

    Then you simply create a SFAuthenticationSession and start it.

    let authURL = "https://the.service.you/want/toAuthorizeWith?..."
    let scheme = "YOURSCHEME://"
    let authSession = SFAuthenticationSession(url: authURL, callbackURLScheme: scheme, completionHandler: completion)
    authSession.start()
    

    iOS 10 and before

    As some have noted in the comments, the accepted answer is incomplete and won't work on its own. When strolling through Strawberry Code's blog post one can find a link to the related GitHub project. That project's README.MD explains a crucial part of the setup, namely adding the redirect URI to Info.plist. So the whole thing goes down like this:

    AppDelegate.swift

    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    
       if let sourceApplication = options[.sourceApplication] {
           if (String(describing: sourceApplication) == "com.apple.SafariViewService") {
                NotificationCenter.default.post(name: Notification.Name("CallbackNotification"), object: url)
                return true
            }
        }
    
        return false
    }
    

    ViewController.swift

    Register for the notification to call your handler in some sensible place. I'd recommend not doing it in viewDidLoad() but only before you are actually presenting the SFSafariViewController.

    NotificationCenter.default.addObserver(self, selector: #selector(safariLogin(_:)), name: Notification.Name("CallbackNotification"), object: nil)
    let safariVC = SFSafariViewController(URL: authURL)
    safariVC.delegate = self
    self.present(safariVC, animated: true, completion: nil)
    

    And then remove the observance in the handler:

    @objc func safariLogin(_ notification : Notification) {
    
        NotificationCenter.default.removeObserver(self, name: Notification.Name("CallbackNotification"), object: nil)
    
        guard let url = notification.object as? URL else {
            return
        }
    
        // Parse url ...
    
    }
    

    Remember that the user is able to dismiss the SFSafariViewController by tapping the Done button, so be sure to adopt the SFSafariViewControllerDelegate protocol and remove the observance like this as well:

    func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
        NotificationCenter.default.removeObserver(self, name: Notification.Name("CallbackNotification"), object: nil)
    }
    

    Info.plist

    To make it all work you need to add your redirect URI scheme to Info.plist:

    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLName</key>
            <string>com.YOUR.BUNDLE.IDENTIFIER</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>YOURSCHEME</string>
            </array>
        </dict>
    </array>
    

    Of course YOURSCHEME has to match the scheme of the redirect URI you registered with the web service you're trying to oAuthorizing with.

    0 讨论(0)
提交回复
热议问题