はじめに
2020年4月末までの実装が急がれる「Sign in with Apple」ですが、みなさん対応はお済みでしょうか?
■Sign in with Apple機能について
「Sign in with Apple」はソーシャルログインの一種で、Apple IDを用いてアプリへの会員登録やログイン認証を行うことができます。
また、以下のApple社からの告知により、第三者ソーシャルログイン(Facebook, Twitterなど)を採用するアプリは、同様に「Sign in with Apple」にも対応する必要があります。(App Storeの審査ガイドライン)
ニフクラ mobile backend(以下、NCMB)の SNS連携機能にも 2020年3月26日に「Sign in With Apple」連携機能が無事リリースされています。ここでは、SwiftUIとNCMBを使った「Sign in with Apple」の実装方法を解説します
- 実装に取り掛かる前に、Sign in with Apple を実装するために必要な証明書の作り方 - Qiitaを見て、「Sign in with Apple」の__実装に必要なAppleの証明書類の準備__をしてから取り組んでください
NCMBの「Sign in with Apple」連携機能について
NCMBを使わなくとも「Sign in with Apple」の実装は可能ですが、次のような場合にNCMBとの連携が有効になります:
- 一般的なユーザー認証方法(例えばメールアドレスとパスワードでの認証)をNCMBを使って実装している(あるいは実装予定)場合
- NCMBを使ってプッシュ通知を実装(あるいは実装予定)していて、ユーザー属性に応じたプッシュ通知の配信をしたい場合
など
「Sign in with Apple」は認証のみ(ユーザー情報など保持しない)ですが、NCMBと連携することでNCMB上にユーザー情報を作成することができます。つまり、NCMB上でユーザーを管理することができるようになるため、NCMBの別の認証方法(メールアドレス・パスワード認証など)のユーザーと一緒に管理することが可能になります。
また、NCMB上に作成されたユーザー情報には属性など必要に応じて追加情報を付与することができるので、NCMBを使ったプッシュ通知の配信時に活用することも可能です
※厳密に言うと、プッシュ通知の配信端末情報とユーザー情報とは異なるため、プッシュ通知配信にユーザー属性を利用する場合は、それらを関連付けてから利用することになります。
認証のみの実装であればNCMBは不要ですが、気軽にユーザー管理が行えるメリットを考えると、NCMBとの連携がおすすめです
環境準備
私の動作環境を記載しておきます。
- NCMBのアカウント
- SNSアカウントを取得(無料のBasicプランを利用できます)
- Mac OS 10.15.4
- Xcode 11.4
- iPhoneSE 13.4 ※動作確認はSimulatorでもOK
- iOS13以上じゃないと Sign in With Apple は動作しません
- Sign in with Apple を利用するために必要な証明書類
- Sign in with Apple を実装するために必要な証明書の作り方 - Qiitaで作成したもの一式(下図参照)
実装内容
既存アプリへの追加実装も可能ですが、ここではまっさらの状態からプロジェクトを作成し実装する手順を解説します。
- NCMBにアプリを作成する
- NCMBに各種IDと秘密鍵を設定する
- Xcodeにプロジェクトを作成する
- XcodeプロジェクトとNCMBアプリを連携する
- Xcodeプロジェクトに「Sign in with Apple」を実装する
- Xcodeプロジェクトの設定をする
- 動作確認をする
実装手順
1. NCMBにアプリを作成する
まずはNCMB(サーバー側)の準備をしましょう。NCMBの管理画面にログインします。
- アカウント未取得の場合はこちらから取得し、初回ログインと規約に同意まで実施します
- SNSアカウントで取得すると無料のBasicプランを利用できます
- ログイン後アプリ作成の画面が表示されますので、アプリ名を入力し、「新規作成」をクリックします (例
SignInWithAppleTest
)- すでにアカウントを保持かつアプリが存在する場合は、右上の「+新しいアプリ」をクリックしてアプリを作成します
- アプリが作成されるとAPIキー(アプリケーションキーとクライアントキー)が発行されます
- 後ほどXcode側に埋め込んでいきますが、今は使用しないのでなにもせずに「OK」をクリックします
- ダッシュボードが表示されます
- 「Sign in with Apple」実装後、ユーザー情報が格納される場所を確認しておくため、「会員管理」をクリックします
- まだ空っぽの状態ですがここにユーザー情報が表示され管理できるようになります
2. NCMBに各種IDと秘密鍵を設定する
Sign in with Apple を実装するために必要な証明書の作り方 - Qiitaで作成したIDと秘密鍵(❸〜❺)を設定していきます。
※作成がまだの場合はこちらで作成をお願いします
- 管理画面右上にある「アプリ設定」をクリックします
- 「SNS連携」をクリックして画面を下までスクロールすると、「Apple ID連携」の項目が確認できます
- 「許可する」を選択します
- ❸、❹で作成・確認・発行した「Bundle ID」、「Team ID」、「Key ID」をそれぞれ該当箇所に記入します
- 「保存する」をクリックします
- 「秘密鍵の選択」をクリックして、❺でダウンロードした「秘密鍵.p8」を選択し、「アップロード」をクリックします
- 全て正しく選択されると次のようになります
これでNCMB側の準備は完了です
3. Xcodeにプロジェクトを作成する
スマホアプリ側の準備をしていきます。
- 細かいところはなんでもいいですが、ここでは次のように作りました
- 「Product Name」を記入します
- 基本は「Bundle ID」に合わせますが、あとで修正できるのでここではあまり気にせず自由に記入してOKです
- 保存場所を指定するとプロジェクトが作成され、Xcodeに表示されます
- 左上の「×」をクリックして一旦プロジェクトを閉じましょう
4. XcodeプロジェクトとNCMBアプリを連携する
まず、NCMBの Swift SDK を導入します。SDKの導入方法はいくつかありますが、ここではCocoaPodsを利用して導入していきます。
- ターミナルを開きます
- CocoaPodsは無ければ導入します
# cocoaPodsのインストール
$ sudo gem install cocoapods
# cocoaPodsのセットアップ
$ pod setup
# バージョン確認
$ pod --version
- 先ほど作成したプロジェクト「.xcodeproj」のディレクトリに移動して、Podfileを作成します
# ディレクトリ移動
$ cd [プロジェクトパス]
# Podfileの作成
$ pod init
- 作成されたPodfileを開いて、以下の内容に書き換え、保存します
platform :ios, '10.0'
target "YOUR_APP_TARGET" do
pod 'NCMB', :git => 'https://github.com/NIFCLOUD-mbaas/ncmb_swift.git'
end
YOUR_APP_TARGET
の部分は、作成しているXcodeプロジェクトのプロジェクト名(Product Name)に書き換えてください
- Podfileに書いたSDKをインストールします
# podfileに記載された内容でインストール
$ pod install
- インストールが完了すると、「.xcodeproj」と同じフォルダに、SDKを導入したプロジェエクト「.xcworkspace」が作成されます
- 「.xcworkspace」をダブルクリックして開きます
- 元からある青い方ではなく白い方です。以後起動は必ず「.xcworkspace」から行います
次に導入したSDKの初期化を行います。
- 「AppDelegate.swift」を次のように編集します
import UIKit
import NCMB // SDKの読み込み
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
// APIキー
let applicationKey = "YOUR_APPLICATION_KEY"
let clientKey = "YOUR_CLIENT_KEY"
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// SDK初期化
NCMB.initialize(applicationKey: applicationKey, clientKey: clientKey);
return true
}
// 以下変更なしのため省略
}
もしimport NCMB
にエラーが出る場合は、一度Buildすることで解消します
-
YOUR_APPLICATION_KEY
とYOUR_CLIENT_KEY
は、NCMBでアプリ作成時に発行されたAPIキーに置き換える必要があります - APIキーはNCMBの管理画面右上にある「アプリ設定」>「基本」で確認できます
画面に表示されている部分は一部で、APIキーは非常に長いのでコピーをとるときは必ず「コピー」ボタンを活用しましょう
APIキーはNCMBのアプリを利用するための認証用の鍵です。APIキーが外部に流出してしまうと、データにアクセスされてしまう危険があるため、流出しないよう管理には十分ご注意ください
これでXcodeプロジェクトとNCMBとの連携が完了しました
5. Xcodeプロジェクトに「Sign in with Apple」を実装する
まずは「Sign in with Apple」ボタンを設置します。既存の「ContentView.swift」に置いてみましょう。ボタンの作成のため、新規でswiftファイルを1つ作成します。
- 「ContentView.swift」で右クリックをして「New File」をクリックします
- 「Swift File」をクリックして「Next」をクリックします
- ファイル名を指定して作成します(例
SignInWithApple.swift
)
- 作成したファイル(以下「SignInWithApple.swift」と呼びます)を開き、次のように実装します
import SwiftUI
import AuthenticationServices
struct setupProviderLoginView: UIViewRepresentable {
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
let setupProviderLoginView = ASAuthorizationAppleIDButton()
return setupProviderLoginView
}
func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {
}
}
元々用意されている「Sign in with Apple」ボタンを利用するためにの処理が書かれています
それではボタンを配置しましょう。
- 「ContentView.swift」を開き、次のように実装します。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
setupProviderLoginView()
.frame(width: 200.0, height: 50.0)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
- 実装できたらXcode右側にある「Resume」ボタンをクリックしてプレビューを確認しましょう
- こんな感じで表示されていればOKです
ボタンが用意できたので、「Sign in with Apple」の処理を実装していきましょう。
- 「SignInWithApple.swift」を開き、次のようコードを更新します
- 「Sign in with Apple」ボタンのタップイベントを実装するために、イベントを受け取るための
Coordinator
クラスを作成しています
import SwiftUI
import AuthenticationServices
struct setupProviderLoginView: UIViewRepresentable {
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
let authorizationButton = ASAuthorizationAppleIDButton()
// 「Sign in with Apple」ボタン押下時のイベント設定
/*(2)*/
return authorizationButton
}
func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {
}
// Coordinatorを作成する処理
func makeCoordinator() -> Coordinator {
// 初期値にView自身を渡す
Coordinator(self)
}
}
// ボタンがタップされたときイベントを受け取るためのクラス
final class Coordinator: NSObject {
var parent: setupProviderLoginView
init(_ parent: setupProviderLoginView) {
self.parent = parent
super.init()
}
// 「Sign in with Apple」ボタンがタップされたときの処理
@objc func handleAuthorizationAppleIDButtonPress() {
/*(1)*/
}
}
extension Coordinator: ASAuthorizationControllerDelegate {
// 認証処理が成功した時のコールバック
func authorizationController(controller _: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
/*(3)*/
}
// 認証処理が失敗またはキャンセルボタンが押されたときのコールバック
func authorizationController(controller _: ASAuthorizationController, didCompleteWithError error: Error) {
/*(4)*/
}
}
extension Coordinator: ASAuthorizationControllerPresentationContextProviding {
// 認証ダイアログを表示するためにUIWindowを返すためのコールバック
func presentationAnchor(for _: ASAuthorizationController) -> ASPresentationAnchor {
let vc = UIApplication.shared.windows.last?.rootViewController
return (vc?.view.window!)!
}
}
- (1)〜(4)を順番に実装していきます
(1)「Sign in with Apple」ボタンがタップされたときの処理
「Sign in with Apple」ボタンがタップされたときの処理をCoordinator.handleAuthorizationAppleIDButtonPress
メソッドに実装します。
// 「Sign in with Apple」ボタンがタップされたときの処理
@objc func handleAuthorizationAppleIDButtonPress() {
// Apple IDによる認可のリクエスト
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
これにより、処理内容は以下です:
- ユーザーの氏名とEメールアドレスに対する認可リクエストを実行して認証フローを開始する
- システムは、ユーザーが自分のApple IDでデバイスにサインインしているかどうかをチェックする
- ユーザーがシステムレベルでサインインしていない場合、Appはユーザーが「設定」でApple IDによるサインインを行うよう、メッセージを表示する
※「Appleでサインイン」によるユーザー認証の実装 - 日本語ドキュメント - Apple Developerより抜粋
(2)「Sign in with Apple」ボタン押下時のイベント設定
(1)で実装したhandleAuthorizationAppleIDButtonPress
メソッドを「Sign in with Apple」ボタン押下時に呼び出す処理を実装します。
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
let authorizationButton = ASAuthorizationAppleIDButton()
// 「Sign in with Apple」ボタン押下時のイベント設定
authorizationButton.addTarget(context.coordinator, action: #selector(Coordinator.handleAuthorizationAppleIDButtonPress), for: .touchUpInside)
return authorizationButton
}
(3)認証処理に成功した時のコールバック(★NCMBとの連携部分)
認証処理が完了するとASAuthorizationControllerDelegate
が呼び出され、認証に成功した場合はauthorizationController
メソッドが呼ばれます。ここで、NCMBにユーザー情報の保存処理を実装していくことになります。
そのため、「SignInWithApple.swift」の冒頭でSDKの読み込みを追記してから実装する必要があります。
import NCMB
// 認証処理に成功した時のコールバック
func authorizationController(controller _: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
print("AppleID > 認証完了")
// mobile backend に会員登録・認証を行う準備します
let authorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: String.Encoding.utf8) ?? "Data could not be printed"
//NCMBAppleParametersで発行された認証情報を指定します
let appleParam = NCMBAppleParameters(id: appleIDCredential.user, accessToken: authorizationCode)
// mobile backendに会員登録・認証を行います
NCMBUser().signUpWithAppleToken(appleParameters: appleParam, callback: { result in
switch result {
case .success:
print("NCMB > 認証完了")
case let .failure(error):
print("NCMB > 認証エラー: \(error)")
}})
default:
break
}
}
- AppleID認証に成功した際、認証情報を
NCMBAppleParameters
に設定し、NCMBUser(). signUpWithAppleToken
でNCMB上にAppleID認証ユーザーとして格納および認証処理を行います - Apple側の認証の後、NCMB側でも認証を行うため、コールバックも二重になっています
(4)認証処理が失敗またはキャンセルボタンが押されたときのコールバック
認証失敗またはキャンセルボタンが押された場合はASAuthorizationControllerDelegate
のauthorizationController
メソッドが呼ばれます。
// 認証処理が失敗またはキャンセルボタンが押されたときのコールバック
func authorizationController(controller _: ASAuthorizationController, didCompleteWithError error: Error) {
print("AppleID > 認証エラー: \(error)")
}
これで実装は完了です。
※「SignInWithApple.swift」の完成版は最後に掲載します
エラーが出ている場合も正しく実装できていればビルド時に消えますのでそのままでOKです
6. Xcodeプロジェクトの設定をする
動作確認の前に、Xcode側にも設定が必要です。
- プロジェクトをクリックして「Signing&Capabilities」をクリックします
- 証明書類を作成したApple Developper ProgramのTeamを選択し、「Bundle identifier」の項目が❸で作成した「Bundle ID」と一致しているか確認します
- 組み合わせが間違っているとエラーが出ます
- 設定できたら「+Capability」をクリックします
- 一覧から「Sign In with Apple」を見つけ、ダブルクリックします
下図のように設定されれば完了です
7. 動作確認をする
実機またはSimulatorでBuildします。ここではiPhone8のSimulatorで動作確認をした物を掲載します。
- 「Sign in with Apple」ボタンをタップしてみましょう
ちなみに初回は「設定」からApple IDの設定が必要ですので登録をしましょう。ログインがキャンセルされるとログにはエラーが出ますが気にしないでOKです。
- 説明画面が出るので、スクロールして一番下にある「続ける」をタップします
- 確認画面が出るので、メールを共有またはメールを非公開のどちらかを選択して「パスワードで続ける」をクリックします
- Apple IDのパスワードを入力して「続ける」をクリックします
- 正しくログイン処理ができるよ以下のようなログがXcodeに書き出されます
AppleID > 認証完了
13.4
NCMB > 認証完了
ログの見方とエラー例:
-
AppleID >
・・・ Apple側の認証に関するログ -
NCMB >
・・・ NCMB側の認証に関するログ
AppleID > 認証エラー: Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1001 "(null)"
NCMB > 認証エラー: NCMBApiError(code: "E403005", error: "apple must not be entered.")
NCMB > 認証エラー: NCMBApiError(code: "E401003", error: "OAuth apple authentication error.")
- ログインに成功すると、同時にNCMBに新しいユーザー情報が作成されます
- authDataフィールドには以下のデータが格納されます
{"apple":{"id":"******","client_id":"jp.fujitsu.fjct.ncmb.appletestapp","access_token":"******"}}
おわりに
みなさん、最後までたどり着けましたでしょうか?
どうしても手順が多いので記事が長くなってしまいますね、、、わかりにくいところあればぜひコメントください
ただ手順が多い原因はNCMBではなくAppleの実装や準備部分になりますね。
正直NCMB部分は大した作業量はありません。なので、後から追加することも容易です
ぜひ合わせて実装してみてもらえたら嬉しいですね
あと、現時点ではボタンの実装後の画面遷移などは実装していません
(余力があれば更新するかもしれないです乞うご期待)
完成版「SignInWithApple.swift」
//
// SignInWithApple.swift
// appletestapp
//
// Created by natsumo on 2020/04/15.
// Copyright © 2020 natsumo. All rights reserved.
//
import SwiftUI
import AuthenticationServices
import NCMB
struct setupProviderLoginView: UIViewRepresentable {
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
let authorizationButton = ASAuthorizationAppleIDButton()
// 「Sign in with Apple」ボタン押下時のイベント設定
authorizationButton.addTarget(context.coordinator, action: #selector(Coordinator.handleAuthorizationAppleIDButtonPress), for: .touchUpInside)
return authorizationButton
}
func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {
}
// Coordinatorを作成する処理
func makeCoordinator() -> Coordinator {
// 初期値にView自身を渡す
Coordinator(self)
}
}
// ボタンがタップされたときイベントを受け取るためのクラス
final class Coordinator: NSObject {
var parent: setupProviderLoginView
init(_ parent: setupProviderLoginView) {
self.parent = parent
super.init()
}
// 「Sign in with Apple」ボタンがタップされたときの処理
@objc func handleAuthorizationAppleIDButtonPress() {
// Apple IDによる認可のリクエスト
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
}
extension Coordinator: ASAuthorizationControllerDelegate {
// 認証処理に成功した時のコールバック
func authorizationController(controller _: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
print("AppleID > 認証完了")
// mobile backend に会員登録・認証を行う準備します
let authorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: String.Encoding.utf8) ?? "Data could not be printed"
//NCMBAppleParametersで発行された認証情報を指定します
let appleParam = NCMBAppleParameters(id: appleIDCredential.user, accessToken: authorizationCode)
// mobile backendに会員登録・認証を行います
NCMBUser().signUpWithAppleToken(appleParameters: appleParam, callback: { result in
switch result {
case .success:
print("NCMB > 認証完了")
case let .failure(error):
print("NCMB > 認証エラー: \(error)")
}})
default:
break
}
}
// 認証処理が失敗またはキャンセルボタンが押されたときのコールバック
func authorizationController(controller _: ASAuthorizationController, didCompleteWithError error: Error) {
print("AppleID > 認証エラー: \(error)")
}
}
extension Coordinator: ASAuthorizationControllerPresentationContextProviding {
// 認証ダイアログを表示するためにUIWindowを返すためのコールバック
func presentationAnchor(for _: ASAuthorizationController) -> ASPresentationAnchor {
let vc = UIApplication.shared.windows.last?.rootViewController
return (vc?.view.window!)!
}
}