LoginSignup
76
57

More than 3 years have passed since last update.

FlutterとCloud Functions、Firebase Authを使ってSign In with Appleを実装してみた

Last updated at Posted at 2019-09-29

どうやら Sign in with Appleはサードパーティログインを実装している場合は必須で実装しないといけないみたいですね。

自分のアプリではログインの処理はFlutterとFirebaseで完結させたかったので実装してみました。

完成系はこんな感じです。(あくまで参考に)

まずは準備

まずはXcodeの設定をしないといけないので、こちらの素晴らしい記事を参考に実装してください。

この記事では割愛します。

Flutter側の準備

次にFlutter側に Sign In with Appleのプラグインがあったので、こちらを導入します。

pubspec.ymlに下記を追加(バージョンについては最新のものをお使いください。)

apple_sign_in: ^0.0.3

ボタンを出す

それではログインボタンを実装します。
実装方法は下記


import 'dart:io';

import 'package:apple_sign_in/apple_sign_in.dart';

class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> with RouteAware {
  final Future<bool> _isAvailableFuture = AppleSignIn.isAvailable();

  @override
  void initState() {
    super.initState();
    AppleSignIn.onCredentialRevoked.listen((_) {
      print("Credentials revoked");
    });
  }

  @override
  Widget build(BuildContext context) {
    return _login();
  }

  Widget _login() {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(),
      body: Container(
        width: MediaQuery.of(context).size.width,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Platform.isIOS
                ? FutureBuilder(
                    future: _isAvailableFuture,
                    builder: (_, isAvailableSnapshot) {
                      if (!isAvailableSnapshot.hasData) return SizedBox();

                      return Container(
                        margin: const EdgeInsets.only(
                          left: 50,
                          right: 50,
                        ),
                        child: isAvailableSnapshot.data
                            ? AppleSignInButton(
                                onPressed: () {
                                   // ログイン処理を実装する
                                }
                            : SizedBox(),
                      );
                    },
                  )
                : const Text("This Platform is the iOS")
          ],
        ),
      ),
    );
  }
}

これでボタンが表示されます。下がボタンが表示された例です。

もしかしたらこのレイアウトだと、Appleの審査に弾かれてしまう可能性があるのでガイドラインを読んで適宜修正してください。
https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/

リクエスト処理

次にリクエスト処理を実装します。

  Future doAppleLogin() async {
    final AuthorizationResult result = await AppleSignIn.performRequests([
      AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
    ]);

    switch (result.status) {
      case AuthorizationStatus.authorized:
        print("success");
        print(result.credential.user);
        // ログイン成功

        break;

      case AuthorizationStatus.error:
        print("Sign in failed: ${result.error.localizedDescription}");

        throw Exception(result.error.localizedDescription);
        break;

      case AuthorizationStatus.cancelled:
        print('User cancelled');
        break;
    }
  }

Cloud Functionsの実装

次にカスタム認証するためのトークンの取得をCloud Functionsで行います。
こちらのコードは下記の記事を参考に実装しました。(下の記事のコードの方がとても綺麗で安全なコードですので、そっちを参考にしたほうがいいと思います)

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();

export const onSignInWithApple = functions.https.onCall(
  async (data, context) => {
    const uid = data.userIdentifier;
    const email = data.email;
    const customToken = await admin.auth().createCustomToken("Apple" + uid);
    if (!email) {
      return {
        custom_token: customToken
      };
    }
    await admin.auth().updateUser(uid, {
      email: email
    });
    return {
      custom_token: customToken
    };
  }
);

書いたらデプロイします。

Flutter側から呼び出し

次にこのエンドポイントをFlutterから呼び出します。
エンドポイントですので、普通にAPIを叩くようにしてもいいのですが、Cloud Functionsを呼び出すプラグインがあるので、せっかくなのでこちらを使いましょう

pubspec.ymlに下記を追加(バージョンについては最新のものをお使いください)

cloud_functions: ^0.4.1+1

ログインが成功したらCloud Functionsを呼び出します。

  Future doAppleLogin() async {
    final AuthorizationResult result = await AppleSignIn.performRequests([
      AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
    ]);

    switch (result.status) {
      case AuthorizationStatus.authorized:
        print("success");
        print(result.credential.user);
        CloudFunctions functions = CloudFunctions.instance;
        HttpsCallableResult httpCallResult = await functions
            .getHttpsCallable(functionName: "onSignInWithApple")
            .call({
          "email": result.credential.email,
          "userIdentifier": result.credential.user,
        });

        FirebaseUser user = await _signInToFirebaseFromApple(
            httpCallResult.data["custom_token"]);

        // ログイン後の処理

        break;

      case AuthorizationStatus.error:
        print("Sign in failed: ${result.error.localizedDescription}");

        throw Exception(result.error.localizedDescription);
        break;

      case AuthorizationStatus.cancelled:
        print('User cancelled');
        break;
    }
  }

  Future<FirebaseUser> _signInToFirebaseFromApple(String token) async {
    FirebaseAuth auth = FirebaseAuth.instance;
    final result = await auth.signInWithCustomToken(token: token);
    return result.user;
  }


これで実装完了です。

追記

もしここでCLoud Functionsを呼び出した時にエラーが発生した場合はIAM関連で必要な権限が設定されていない可能性があります。
下記のツイートで解決しますので、詳しくはこちらをどうぞ

今不明なこと

このツイートについてもし知ってる方いらっしゃれば、コメント欄にてご連絡ください。

76
57
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
76
57