I think, as a developer, I’ve never been so excited and frustrated at the same time by the same thing. Sorry for the non-tech reading that, you can stop here, it’s fine ;) My oh-so-frustrating thing is the brand new SFSafariViewController
introduced in WWDC 2015, and especially its interaction with OAuth
.
If you are reading this article, you have probably watched WWDC’s session 504: Introduction to Safari View Controller. And you’ve probably browsed through a lot of articles arguing the pros and cons of SFSafariViewController
.
Here, I would like to jump straight to the “Web-based authentication” section, when Ricky Mondello showed us the slide below, while saying:
“Because since Safari View Controller has access to a user’s credentials, synced across all of their devices with iCloud Keychain, logging in is going to be a breeze.[…]
It takes two steps.
The first is where you would’ve used your own in-app browser, just present an instance of SFSafariViewController.
And once the user is finished logging in and the third-party web service redirects back to your app with the custom URL scheme that you fed it, you can accept that in your AppDelegate
‘s handleOpenURL
method.
From there you can inspect the response and dismiss the instance of SFSafariViewController
because you know that the authentication is done.
That’s it.
Two steps.”
Source: http://asciiwwdc.com/2015/sessions/504#t=1558.026

Intriguing. Two steps? That sounds nearly too easy. I wanted to see some implementations of it. While searching the web, I found that very interesting article from Rizwan Sattar: How iOS 9’s Safari View Controller could completely change your app’s onboarding experience. All of you who have submitted apps to the AppStore know that Apple doesn’t really like the opening of a third-party app in the login workflow. And the “Safari bounce” is not very user-friendly… So let’s have a look at SFSafariViewController
, it may solve our dilemma.
Without further ado, let’s do it!
Two steps, let’s see…
- Call the
SFSafariViewController
with your favorite login URL - In the
AppDelegate
(application:HandleOpenUrl
), when you have parsed the response, dismiss theSFSafariViewController
.
That sounds so simple, yet it feels like there a little something missing when you try to implement it. Strangely enough, I couldn’t find any tutorial or example of the implementation of OAuth
in SFSafariViewController
. I searched the web, GitHub, even Twitter: nothing. So I dug into it, and came to a “viable solution”: a communication via Notification between the AppDelegate
and the ViewController
. It is not revolutionary, but it works like a charm (if you have any suggestions, please leave a comment below or on Twitter @cath_schwz :)
In your ViewController
Here is the basic setup of your ViewController
: import SafariServices
, SFSafariViewControllerDelegate
and its finish method, declare a name (constant) for our notification. And let’s create an outlet to a button and a SFSafariViewController
variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import SafariServices let kCloseSafariViewControllerNotification = "kCloseSafariViewControllerNotification" class ViewController: UIViewController, SFSafariViewControllerDelegate { @IBOutlet var loginButton: UIButton! var safariVC: SFSafariViewController? // // Magic goes here // // MARK: - SFSafariViewControllerDelegate // Called on "Done" button func safariViewControllerDidFinish(controller: SFSafariViewController) { controller.dismissViewControllerAnimated(true, completion: nil) } } |
Then, let’s create the method called when you tap on the button: the one that will open the SFSafariViewController
:
1 2 3 4 5 6 7 |
// MARK: - Action @IBAction func loginButtonTapped(sender: AnyObject) { let authURL = NSURL(string: "") // the URL goes here safariVC = SFSafariViewController(URL: authURL) safariVC!.delegate = self self.presentViewController(safariVC!, animated: true, completion: nil) } |
At that point, if your outlet and action are correctly linked to your view, you should be able to display and dismiss manually the SFSafariViewController
. Don’t forget to put a URL. Any URL will do for now.
Now let’s add the magic: in viewDidLoad
, add an observer that will trigger the safariLogin
method when the notification is sent. It’s in your safariLogin
method that you will deal with the OAuth response and “automatically” dismiss the SFSafariViewController
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
override func viewDidLoad() { super.viewDidLoad() NSNotificationCenter.defaultCenter().addObserver(self, selector: "safariLogin:", name: kCloseSafariViewControllerNotification, object: nil) } func safariLogin(notification: NSNotification) { // get the url form the auth callback let url = notification.object as! NSURL // then do whatever you like, for example : // get the code (token) from the URL // and do a request to get the information you need (id, name, ...) // Finally dismiss the Safari View Controller with: self.safariVC!.dismissViewControllerAnimated(true, completion: nil) } } |
In your AppDelegate
We just saw what happens when the ViewController
receives a notification. Now let’s see how the AppDelegate
sends it. Easy, no surprises:
1 2 3 4 5 6 7 8 9 |
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool { // just making sure we send the notification when the URL is opened in SFSafariViewController if (sourceApplication == "com.apple.SafariViewService") { NSNotificationCenter.defaultCenter().postNotificationName(kCloseSafariViewControllerNotification, object: url) return true } return true } |
What about OAuth?
We just covered the easiest part: displaying and dismissing the SFSafariViewController
. Now that you have that running, we can add the OAuth component. And to illustrate my example I’ve chosen Instagram. There is no iOS SDK and no specific way to login via Instagram except by displaying a web view, so it makes the perfect candidate for our study.
I have put the whole project on GitHub: SafariOauthLogin. Don’t hesitate to fork it and to play with it. Just follow the instructions and it should be working out of the box :)
Inside the box
I would like to draw your attention on 2 little things:
- The
OAuth
magic that I’m using for Instagram comes from here. You will find it in theAuth.swift
file. It’s pretty short and clean. I like it. - In the
safariLogin
method, I mention a token called “code” that comes form the response URL. That “code” needs to be extracted in order to be used to “trade” actual information from your third-party API. I do so thanks to the method calledextractCode
Further reading
I would love to have a one size fits all for OAuth logins, but something light enough that it could be kept up to date with the third-parties’ APIs and wouldn’t break at each and every update. One can dream…
I know there are pretty cool OAuth tools out there, for example: OAuthSwift, OAuth2, aerogear-ios-oauth2. But none of them is using SFSafariViewController
yet, except mwermuth who has forked OAuthSwift to use the SFSafariViewController
on Instagram login too. I will definitely keep an eye on that!
Two more words, bear with me:
Reddit has a nice OAuth tutorial. I believe it can be easily tweaked to use the SFSafariViewController
instead of the web view
.
And finally, if you want a comprehensive OAuth 2 with Swift tutorial, you’ll be in good hands there. You may want to keep it for an other day though, it’s quite a long read too!
That’s all folks! I hope you enjoyed it and that you have found some answers there. Happy coding!