Help us understand the problem. What is going on with this article?

Sign in with Apple with Firebase Authentication

※Sign in with Appleはまだbeta版の為、仕様等に変更が入る可能性があります

WWDC 2019でSign in with Appleの機能が発表されました。
この機能はApple IDを利用して様々なアプリやサービスにログインが可能となる物です。
但し、ログインが必要なアプリやサービスはアプリ単体で完結する物は少なく、基本的にはサーバー上のデータと連携する必要があるかと思います。
ここではFirebase Authenticationを利用してアプリとサーバーのユーザー情報を連携させてみようと思います。

事前準備

Apple DeveloperページのIdentifiersSign in with Appleを有効にします。

image.png

Xcode上でもプロジェクトのSigning & CapabilitiesSign in with Appleを追加します。

ログインボタンの設置

基本的にはOSが用意したボタンを用意するのが良いかと思いますが、カスタマイズしたい場合はHIGを確認してガイドラインに沿うか確認した方が良いでしょう。

import AuthenticationServices

let appleIdButton = ASAuthorizationAppleIDButton(authorizationButtonType: .signUp, authorizationButtonStyle: .whiteOutline)
appleIdButton.addTarget(self, action: #selector(handleTap(_:)), for: .touchUpInside)
view.addSubview(appleIdButton)

ログインボタンのイベントハンドリング

@objc
func handleTap(_ sender: ASAuthorizationAppleIDButton) {
    let request = ASAuthorizationAppleIDProvider().createRequest()
    request.requestedScopes = [.fullName, .email]
    let controller = ASAuthorizationController(authorizationRequests: [request])
    controller.delegate = self
    controller.presentationContextProvider = self
    controller.performRequests()
}

ログインのdelegateを実装

extension ViewController: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
    func authorizationController(controller _: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        switch authorization.credential {
        case let appleIdCredential as ASAuthorizationAppleIDCredential:
            // TODO: use appleIdCredential.user, email: appleIdCredential.email
        default:
            break
        }
    }

    func authorizationController(controller _: ASAuthorizationController, didCompleteWithError error: Error) {
        DispatchQueue.main.async { [weak self] in
           // TODO: Error handling
        }
    }

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

Firebase

環境はNode 8系を利用します。
Firebase toolsがインストールされている前提です。

Firebase Cloud functionsの作成

firebase init functions でCloud functionsを初期化します。
JavaScriptかTypeScriptを聞かれるのでモダンなTyprScriptを選択しておきましょう。
functions/src/index.ts などが生成されているかと思いますので下記の様に修正します。

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

admin.initializeApp();

type AuthRequest = {
    email: string | null,
    userIdentifier: string
};

function isAuthRequest(data: any): data is AuthRequest {
    return typeof data.userIdentifier === 'string';
}

export const signin_with_apple = functions.https.onCall(async (data, context)  => {
    if (!isAuthRequest(data)) {
        throw new Error('invalid request');
    }
    // この書き方なんとかしたい
    // 追記: 良い書き方をコメントで教えてもらったので気になる方はコメントを参照ください
    const request = data as any as AuthRequest;
    const uid = request.userIdentifier;
    const email = request.email;

    const customToken = await admin.auth().createCustomToken(uid);
    if (!email) {
        return {
            custom_token: customToken
        };
    }
    await admin.auth().updateUser(uid, {
        email: email
    });
    return {
        custom_token: customToken
    };
});

開発が完了したら firebase deploy --only functions:signin_with_apple などでデプロイをしておきます。

アプリからCloud functionsの呼び出し

import FirebaseFunctions

Functions.functions().httpsCallable("signin_with_apple").call(["email": appleIdCredential.email, "userIdentifier": appleIdCredential.user]) { result, error in
    if let error = error {
        // TODO: Error handling
        return
    }

    if let customToken = (result?.data as? [String: Any])?["custom_token"] as? String {
        // Success!
        return
    } else {
        // TODO: Error handling
    }
}

上記で受け取ったcustomTokenFirebaseAuthに渡します。

レスポンスを連携

import FirebaseAuth

Auth.auth().signIn(withCustomToken: customToken) { [weak self] _, error in
    if error != nil {
        // TODO: Error handling
        return
    }
    // Success!
}

Apple IDのバリデーション

Apple IDは設定アプリやサイトから簡単に無効化出来るので、必要に応じてApple IDのハンドリングしましょう。

let provider = ASAuthorizationAppleIDProvider()
provider.getCredentialState(forUserID: appleId) { credentialState, _ in
    DispatchQueue.main.async {
        switch credentialState {
        case .authorized:
            // nothing todo
            break
        case .notFound, .revoked:
            // TODO: error handling
            break
        @unknown default:
            fatalError("unknown credentialState")
        }
    }
}

まとめ

Apple IDでのログイン機能をFirebase Authと連携する事が出来ました。
Appleとしては既存アプリが外部ログイン機能を提供している場合、Apple IDでのログイン機能が必要だと言っているので、なるべく早く対応しておきたいですね。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした