A.J. Kueterman
Posted on July 17, 2019
One of the biggest announcements at WWDC 2019 was the new 'Sign In With Apple' feature, where Apple will now provide an authentication email & password to apps on behalf of you and manage them securely using the iCloud Keychain.
Apple continues to push these privacy-focused features especially around authentication to try to disrupt ubiquitous services like Facebook & Google OAuth that make login flows much easier for users - but at the cost of their data going into the hands of ad companies.
As such, a lot of focus has been given to the Authentication services in iOS. We saw it begin last year with the move from auth services inside SafariServices
to the dedicated AuthenticationServices
APIs. This year, iOS 13 leverages AuthenticationServices
to enable the Sign In With Apple APIs, and continue to refine the sign in experience.
A Guide to Changes in ASWebAuthenticationSession
Last year, I talked a bit about OAuth in iOS in my post about Apple's move from SFAuthenticationSession
to ASWebAuthenticationSession
and how I went about converting my iOS app to use the new API. This year, the ASWebAuthenticationSession
has some smaller tweaks to enhance the OAuth sign-in experience across iOS (including iPadOS) devices.
If you're a developer already sweating about the huge array of changes and opportunities coming for developers in 2019, don't fret about this one, it's a small tweak purely for enhancing OAuth experiences across devices.
To set the stage, let's look at a code snippet for ASWebAuthenticationSession
in iOS 12.
//...
var webAuthSession: ASWebAuthenticationSession?
//...
@available(iOS 12.0, *)
func getAuthTokenWithWebLogin() {
let authURL = URL(string: "https://github.com/login/oauth/authorize?client_id=<client_id>")
let callbackUrlScheme = "octonotes://auth"
self.webAuthSession = ASWebAuthenticationSession.init(url: authURL!, callbackURLScheme: callbackUrlScheme, completionHandler: { (callBack:URL?, error:Error?) in
// handle auth response
guard error == nil, let successURL = callBack else {
return
}
let oauthToken = NSURLComponents(string: (successURL.absoluteString))?.queryItems?.filter({$0.name == "code"}).first
// Do what you now that you've got the token, or use the callBack URL
print(oauthToken ?? "No OAuth Token")
})
// Kick it off
self.webAuthSession?.start()
}
In this example, the only thing we need to provide the ASWebAuthenticationSession
is the authentication URL
, a callback URL scheme, and a completion block that handles the result of the OAuth. The OS handles the rest - displaying an alert, launching a Web login flow, and dismissing.
In iOS 13, as Apple continues to refine the multi-app experience for iOS and iPadOS, we now need to help the OS out when it's making the decision on where and how to display the OAuth Alert and Web login flow.
To do that, we have to let the ASWebAuthenticationSession
know which window is presenting the OAuth request. This is done by implementing the ASWebAuthenticationPresentationContextProviding
interface in your presenting View Controller.
The presenting View Controller needs to implement the ASWebAuthenticationPresentationContextProviding
interface and return the relevant window in the presentationAnchor
method.
class LoginViewController: UIViewController, ASWebAuthenticationPresentationContextProviding {
//...
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return self.view.window ?? ASPresentationAnchor()
}
//...
}
Then, when setting up our auth session, we need to specify our presentationContextProvider
delegate.
self.webAuthSession?.presentationContextProvider = context
The full updated method, now passing in a ASWebAuthenticationPresentationContextProviding
context (our presenting VC that implements the ASWebAuthenticationPresentationContextProviding
interface).
//...
var webAuthSession: ASWebAuthenticationSession?
//...
@available(iOS 13.0, *)
func getAuthTokenWithWebLogin(context: ASWebAuthenticationPresentationContextProviding) {
let authURL = URL(string: "https://github.com/login/oauth/authorize?client_id=<client_id>")
let callbackUrlScheme = "octonotes://auth"
self.webAuthSession = ASWebAuthenticationSession.init(url: authURL!, callbackURLScheme: callbackUrlScheme, completionHandler: { (callBack:URL?, error:Error?) in
// handle auth response
guard error == nil, let successURL = callBack else {
return
}
let oauthToken = NSURLComponents(string: (successURL.absoluteString))?.queryItems?.filter({$0.name == "code"}).first
// Do what you now that you've got the token, or use the callBack URL
print(oauthToken ?? "No OAuth Token")
})
// New in iOS 13
self.webAuthSession?.presentationContextProvider = context
// Kick it off
self.webAuthSession?.start()
}
At this point, your ASWebAuthenticationSession
now knows where and how to display your Web-based login flow, and will provide a consistent experience across devices!
Thoughts on OAuth
It has been an interesting experience tinkering with OAuth over the last two years. Right as I was learning the APIs Apple made a big move to new APIs, and this year it's clear how that change is enabling a better sign in experience for users.
Now, in iOS 13, by observing the specific change in this small API, we can see the evolution of iOS to a multi-window experience.
As I see this slow progression play out, I'm beginning to wonder about the future of OAuth in iOS at all. Will web authentication be blacklisted altogether in favor of Sign In With Apple? Will Apple centralize auth in a way that allows me to authenticate with GitHub without going to GitHub services for tokens? Only time will tell, but the changes today can project the changes in the future - stay alert!
If you have any thoughts/questions/predictions about OAuth and Web Authentication with iOS, don't hesitate to reach out to me on Twitter @ajkueterman.
Posted on July 17, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.