LoginSignup
4
6

More than 3 years have passed since last update.

【Sign in with Apple】 SwiftUI×NCMBを使って実装する

Last updated at Posted at 2020-04-16

はじめに

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の審査ガイドライン)

SNS連携機能のApple ID対応リリースについてより

ニフクラ mobile backend(以下、NCMB)の SNS連携機能にも 2020年3月26日に「Sign in With Apple」連携機能が無事リリースされています。ここでは、SwiftUIとNCMBを使った「Sign in with Apple」の実装方法を解説します:thumbsup:

NCMBの「Sign in with Apple」連携機能について

NCMBを使わなくとも「Sign in with Apple」の実装は可能ですが、次のような場合にNCMBとの連携が有効になります:

  • 一般的なユーザー認証方法(例えばメールアドレスとパスワードでの認証)をNCMBを使って実装している(あるいは実装予定)場合
  • NCMBを使ってプッシュ通知を実装(あるいは実装予定)していて、ユーザー属性に応じたプッシュ通知の配信をしたい場合

など

「Sign in with Apple」は認証のみ(ユーザー情報など保持しない)ですが、NCMBと連携することでNCMB上にユーザー情報を作成することができます。つまり、NCMB上でユーザーを管理することができるようになるため、NCMBの別の認証方法(メールアドレス・パスワード認証など)のユーザーと一緒に管理することが可能になります。

また、NCMB上に作成されたユーザー情報には属性など必要に応じて追加情報を付与することができるので、NCMBを使ったプッシュ通知の配信時に活用することも可能です:v:
※厳密に言うと、プッシュ通知の配信端末情報とユーザー情報とは異なるため、プッシュ通知配信にユーザー属性を利用する場合は、それらを関連付けてから利用することになります。

認証のみの実装であればNCMBは不要ですが、気軽にユーザー管理が行えるメリットを考えると、NCMBとの連携がおすすめです:thumbsup::hearts:

環境準備

私の動作環境を記載しておきます。

SigninWithApple連携に必要なものまとめ.png

実装内容

既存アプリへの追加実装も可能ですが、ここではまっさらの状態からプロジェクトを作成し実装する手順を解説します。

  1. NCMBにアプリを作成する
  2. NCMBに各種IDと秘密鍵を設定する
  3. Xcodeにプロジェクトを作成する
  4. XcodeプロジェクトとNCMBアプリを連携する
  5. Xcodeプロジェクトに「Sign in with Apple」を実装する
  6. Xcodeプロジェクトの設定をする
  7. 動作確認をする

実装手順

1. NCMBにアプリを作成する

まずはNCMB(サーバー側)の準備をしましょう。NCMBの管理画面にログインします。

  • アカウント未取得の場合はこちらから取得し、初回ログインと規約に同意まで実施します
    • SNSアカウントで取得すると無料のBasicプランを利用できます
  • ログイン後アプリ作成の画面が表示されますので、アプリ名を入力し、「新規作成」をクリックします (例 SignInWithAppleTest
    • すでにアカウントを保持かつアプリが存在する場合は、右上の「+新しいアプリ」をクリックしてアプリを作成します

スクリーンショット_2020-04-16_13_01_10.png

  • アプリが作成されるとAPIキー(アプリケーションキーとクライアントキー)が発行されます
  • 後ほどXcode側に埋め込んでいきますが、今は使用しないのでなにもせずに「OK」をクリックします

スクリーンショット_2020-04-15_13_52_34.png

  • ダッシュボードが表示されます
  • 「Sign in with Apple」実装後、ユーザー情報が格納される場所を確認しておくため、「会員管理」をクリックします

スクリーンショット_2020-04-15_13_52_58.png

  • まだ空っぽの状態ですがここにユーザー情報が表示され管理できるようになります

スクリーンショット_2020-04-15_13_53_17.png

2. NCMBに各種IDと秘密鍵を設定する

Sign in with Apple を実装するために必要な証明書の作り方 - Qiitaで作成したIDと秘密鍵(❸〜❺)を設定していきます。

SigninWithApple連携に必要なものまとめ.png

※作成がまだの場合はこちらで作成をお願いします:pray:

  • 管理画面右上にある「アプリ設定」をクリックします
  • 「SNS連携」をクリックして画面を下までスクロールすると、「Apple ID連携」の項目が確認できます

スクリーンショット_2020-04-15_13_53_59.png

  • 「許可する」を選択します
  • ❸、❹で作成・確認・発行した「Bundle ID」、「Team ID」、「Key ID」をそれぞれ該当箇所に記入します
  • 「保存する」をクリックします
  • 「秘密鍵の選択」をクリックして、❺でダウンロードした「秘密鍵.p8」を選択し、「アップロード」をクリックします
  • 貼り付けた画像_2020_04_16_13_39.png

  • 全て正しく選択されると次のようになります

スクリーンショット_2020-03-31_15_03_57.png

これでNCMB側の準備は完了です:ok_hand:

3. Xcodeにプロジェクトを作成する

スマホアプリ側の準備をしていきます。

  • 細かいところはなんでもいいですが、ここでは次のように作りました:point_up:

スクリーンショット_2020-04-15_14_24_32.png

  • 「Product Name」を記入します
    • 基本は「Bundle ID」に合わせますが、あとで修正できるのでここではあまり気にせず自由に記入してOKです:ok_hand:

スクリーンショット_2020-04-15_14_25_53.png

  • 保存場所を指定するとプロジェクトが作成され、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を開いて、以下の内容に書き換え、保存します
podfile
platform :ios, '10.0'

target "YOUR_APP_TARGET" do
    pod 'NCMB', :git => 'https://github.com/NIFCLOUD-mbaas/ncmb_swift.git'
end

:warning: YOUR_APP_TARGET の部分は、作成しているXcodeプロジェクトのプロジェクト名(Product Name)に書き換えてください:point_up:

  • Podfileに書いたSDKをインストールします
ターミナル
# podfileに記載された内容でインストール
$ pod install
  • インストールが完了すると、「.xcodeproj」と同じフォルダに、SDKを導入したプロジェエクト「.xcworkspace」が作成されます

スクリーンショット_2020-04-15_14_56_35.png

  • 「.xcworkspace」をダブルクリックして開きます
    • :warning:元からある青い方ではなく白い方です。以後起動は必ず「.xcworkspace」から行います:point_up:

次に導入したSDKの初期化を行います。

  • 「AppDelegate.swift」を次のように編集します
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
    }

// 以下変更なしのため省略

}

:warning:もしimport NCMBにエラーが出る場合は、一度Buildすることで解消します:point_up:

  • YOUR_APPLICATION_KEYYOUR_CLIENT_KEYは、NCMBでアプリ作成時に発行されたAPIキーに置き換える必要があります
  • APIキーはNCMBの管理画面右上にある「アプリ設定」>「基本」で確認できます

スクリーンショット_2020-04-15_15_13_38.png

:warning:画面に表示されている部分は一部で、APIキーは非常に長いのでコピーをとるときは必ず「コピー」ボタンを活用しましょう:point_up:
:warning:APIキーはNCMBのアプリを利用するための認証用の鍵です。APIキーが外部に流出してしまうと、データにアクセスされてしまう危険があるため、流出しないよう管理には十分ご注意ください:pray:

これでXcodeプロジェクトとNCMBとの連携が完了しました:raised_hands:

5. Xcodeプロジェクトに「Sign in with Apple」を実装する

まずは「Sign in with Apple」ボタンを設置します。既存の「ContentView.swift」に置いてみましょう。ボタンの作成のため、新規でswiftファイルを1つ作成します。

  • 「ContentView.swift」で右クリックをして「New File」をクリックします
  • 「Swift File」をクリックして「Next」をクリックします

スクリーンショット_2020-04-15_15_24_03.png

  • ファイル名を指定して作成します(例 SignInWithApple.swift

スクリーンショット 2020-04-15 15.27.39.png

  • 作成したファイル(以下「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」ボタンを利用するためにの処理が書かれています:point_up:
それではボタンを配置しましょう。

  • 「ContentView.swift」を開き、次のように実装します。
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です:ok_hand:

スクリーンショット 2020-04-15 15.37.49.png

ボタンが用意できたので、「Sign in with Apple」の処理を実装していきましょう。

  • 「SignInWithApple.swift」を開き、次のようコードを更新します
  • 「Sign in with Apple」ボタンのタップイベントを実装するために、イベントを受け取るための Coordinatorクラスを作成しています
SignInWithApple.swift
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 メソッドに実装します。

SignInWithApple.swift
// 「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」ボタン押下時に呼び出す処理を実装します。

SignInWithApple.swift
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の読み込みを追記してから実装する必要があります。

SignInWithApple.swift
import NCMB
SignInWithApple.swift
// 認証処理に成功した時のコールバック
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)認証処理が失敗またはキャンセルボタンが押されたときのコールバック

認証失敗またはキャンセルボタンが押された場合はASAuthorizationControllerDelegateauthorizationControllerメソッドが呼ばれます。

SignInWithApple.swift
// 認証処理が失敗またはキャンセルボタンが押されたときのコールバック
func authorizationController(controller _: ASAuthorizationController, didCompleteWithError error: Error) {
    print("AppleID > 認証エラー: \(error)")
}

これで実装は完了です。
※「SignInWithApple.swift」の完成版は最後に掲載します:thumbsup:

:warning:エラーが出ている場合も正しく実装できていればビルド時に消えますのでそのままでOKです:ok_hand:

6. Xcodeプロジェクトの設定をする

動作確認の前に、Xcode側にも設定が必要です。

  • プロジェクトをクリックして「Signing&Capabilities」をクリックします
  • 証明書類を作成したApple Developper ProgramのTeamを選択し、「Bundle identifier」の項目が❸で作成した「Bundle ID」と一致しているか確認します
    • :warning:組み合わせが間違っているとエラーが出ます
  • 設定できたら「+Capability」をクリックします

スクリーンショット_2020-04-15_17_27_56.png

  • 一覧から「Sign In with Apple」を見つけ、ダブルクリックします

スクリーンショット_2020-04-15_17_28_28.png

下図のように設定されれば完了です:ok_hand:

スクリーンショット_2020-04-15_17_28_45.png

7. 動作確認をする

実機またはSimulatorでBuildします。ここではiPhone8のSimulatorで動作確認をした物を掲載します。

  • 「Sign in with Apple」ボタンをタップしてみましょう

スクリーンショット 2020-04-16 10.03.18.png

:warning: ちなみに初回は「設定」からApple IDの設定が必要ですので登録をしましょう。ログインがキャンセルされるとログにはエラーが出ますが気にしないでOKです。

スクリーンショット 2020-04-16 10.03.28.png

  • 説明画面が出るので、スクロールして一番下にある「続ける」をタップします

スクリーンショット.png

  • 確認画面が出るので、メールを共有またはメールを非公開のどちらかを選択して「パスワードで続ける」をクリックします

スクリーンショット_2020-04-16_11_20_40.png

  • Apple IDのパスワードを入力して「続ける」をクリックします

スクリーンショット_2020-04-16_11_23_23.png

  • 正しくログイン処理ができるよ以下のようなログがXcodeに書き出されます
AppleID > 認証完了
13.4
NCMB > 認証完了

:warning: ログの見方とエラー例:

  • AppleID > ・・・ Apple側の認証に関するログ
  • NCMB >・・・ NCMB側の認証に関するログ
例)AppleIDログインをキャンセルしたときに吐き出されるエラー
AppleID > 認証エラー: Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1001 "(null)"
例)NCMB側で事前設定ができていないときに吐き出されるエラー
NCMB > 認証エラー: NCMBApiError(code: "E403005", error: "apple must not be entered.")
NCMB > 認証エラー: NCMBApiError(code: "E401003", error: "OAuth apple authentication error.")
  • ログインに成功すると、同時にNCMBに新しいユーザー情報が作成されます

スクリーンショット_2020-04-16_11_44_44.png

  • authDataフィールドには以下のデータが格納されます
{"apple":{"id":"******","client_id":"jp.fujitsu.fjct.ncmb.appletestapp","access_token":"******"}}

おわりに

みなさん、最後までたどり着けましたでしょうか?:eyes:
どうしても手順が多いので記事が長くなってしまいますね、、、わかりにくいところあればぜひコメントください:pray:
ただ手順が多い原因はNCMBではなくAppleの実装や準備部分になりますね。
正直NCMB部分は大した作業量はありません。なので、後から追加することも容易です:point_up:
ぜひ合わせて実装してみてもらえたら嬉しいですね:heart_eyes:

あと、現時点ではボタンの実装後の画面遷移などは実装していません:joy:
(余力があれば更新するかもしれないです:thumbsup:乞うご期待)

完成版「SignInWithApple.swift」

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!)!
    }
}

参考にした記事・ドキュメント

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6