Posted at

iOS13(beta)でASWebAuthenticationSessionの変更点

注:本記事は Xcode 11 beta 6 (2019/08/20)時点での変更内容での話になります


iOS13でログイン画面が開かない

開発中のアプリでASWebAuthenticationSessionを利用してOAuthログインする機能があり、iOS13(beta)で動作確認をしたところログインボタンを押してログイン画面を出そうとしても出てくれない状態でした。

ログを見てみると以下のエラーの出力がありました。

Cannot start ASWebAuthenticationSession without providing presentation context. Set presentationContextProvider before calling -start.

どうやらiOS13からはstart()を呼び出す前にpresentationContextProviderをセットする必要があるようです。


ASWebAuthenticationPresentationContextProviding

presentationContextProviderASWebAuthenticationPresentationContextProviding(document)というprotocolに適合したNSObjectのサブクラスのインスタンスのようです。

定義は以下のようになっています。

/** @abstract Provides context to target where in an application's UI the authorization view should be shown.

*/

@available(iOS 13.0, *)
public protocol ASWebAuthenticationPresentationContextProviding : NSObjectProtocol {

/** @abstract Return the ASPresentationAnchor in the closest proximity to where a user interacted with your app to trigger
authentication. If starting an ASWebAuthenticationSession on first launch, use the application's main window.
@param session The session requesting a presentation anchor.
@result The ASPresentationAnchor most closely associated with the UI used to trigger authentication.
*/

func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor
}


ASPresentationAnchor

なるほど、そのprotocolのインスタンスを用意すればいいことはわかったけど、ASPresentationAnchorってなんだ?と思ったらUIWindow(iOS, Mac Catalyst, tvOS) or NSWindow(macOS)のtypealiasでした。


直してみる

シンプルに書くと多分こんな感じに実装すれば良さそう

import AuthenticationServices

import UIKit

class ViewController: UIViewController {

private var authenticationSession: ASWebAuthenticationSession?

// ログインボタンを押した後に呼ばれる
func loginStart() {
let authenticationSession = ASWebAuthenticationSession(
url: URL(string: "https://...")!,
callbackURLScheme: "<url-scheme>",
completionHandler: loginCompletionHandler(url:error:))
if #available(iOS 13.0, *) {
//
authenticationSession.presentationContextProvider = self
}
authenticationSession.start()

// authenticationSessionが解放されないようにプロパティに入れておく
self.authenticationSession = authenticationSession
}

// ログインの結果のハンドリング
func loginCompletionHandler(url: URL?, error: Error?) {
authenticationSession = nil
...
}

...
}

@available(iOS 13.0, *)
extension ViewController: ASWebAuthenticationPresentationContextProviding {

func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return view.window!
}
}

ちなみに今開発中のアプリではiOS11も対応しており、ASWebAuthenticationSessionSFAuthenticationSessionをラップしたクラスを定義していたため、ASWebAuthenticationPresentationContextProvidingを適合させたprivateなクラスを用意して実装しました。

このように実装する場合、presentationContextProviderは弱参照なので解放されてしまいます。そのためASWebAuthenticationPresentationContextProvidingを適合したオブジェクトのインスタンスもプロパティとして保持しないといけないことに注意してください。

class AuthenticationSession {

...
private var authenticationSession: Any?
private var presentationContextProvider: Any?

func loginStart(from viewController: UIViewController) {
if #available(iOS 12.0, *) {
let authenticationSession = ASWebAuthenticationSession(
url: loginURL,
callbackURLScheme: "<url-scheme>",
completionHandler: loginCompletionHandler(url:error:))
if #available(iOS 13.0, *) {
let presentationContextProvider = AuthPresentationContextProver(viewController: viewController)
authenticationSession.presentationContextProvider = presentationContextProvider
self.presentationContextProvider = presentationContextProvider
}
authenticationSession.start()
self.authenticationSession = authenticationSession
} else { // iOS 11.x
let authenticationSession = SFAuthenticationSession(
url: loginURL,
callbackURLScheme: "<url-scheme>",
completionHandler: loginCompletionHandler(url:error:))
authenticationSession.start()
self.authenticationSession = authenticationSession
}
}

func loginCompletionHandler(url: URL?, error: Error?) {
authenticationSession = nil
presentationContextProvider = nil
...
}

...

@available(iOS 13.0, *)
private class AuthPresentationContextProver: NSObject, ASWebAuthenticationPresentationContextProviding {

private weak var viewController: UIViewController!

init(viewController: UIViewController) {
self.viewController = viewController
super.init()
}

func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return viewController?.view.window!
}
}
}


おわり

ASWebAuthenticationSessionを使っているアプリはiOS13が出るまでに対応しないとログインが出来なくなってしまう可能性があるので注意しましょう。