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 redirect them and return an OAuth URL with the token for that specific service, e.g. Instagram
RESPONSE: https://www.facebook.com/connect/login_success.html#access_token=BLAHTOKENRESPONSE&expires_in=5114338
When SafariViewController has redirected I want to grab the response URL and store it so I can grab the token. Here is my code:
import SafariServices
let kSafariViewControllerCloseNotification = "kSafariViewControllerCloseNotification"
import UIKit
// facebook OAuth URL for service
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: UIButton) {
safariVC = SFSafariViewController(URL: authURL!)
safariVC!.delegate = self
self.presentViewController(safariVC!, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// not firing the safariLogin function below
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.safariLogin(_:)), name: kSafariViewControllerCloseNotification, object: nil)
}
func safariLogin(notification: NSNotification) {
print("Safari Login call")
// get the url form the auth callback
let url = notification.object as! NSURL
print(url)
self.safariVC!.dismissViewControllerAnimated(true, completion: nil)
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
print("application call")
// just making sure we send the notification when the URL is opened in SFSafariViewController
if (sourceApplication == "com.application.SafariViewTest") {
NSNotificationCenter.defaultCenter().postNotificationName(kSafariViewControllerCloseNotification, object: url)
return true
}
return true
}
}
It opens the authURL and redirects to the correct response URL, but the observer does not fire the safariLogin function to grab the URL. Any help would be much appreciated!
Thanks so much!
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.
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)")
}
}