どうやら Sign in with Appleはサードパーティログインを実装している場合は必須で実装しないといけないみたいですね。
自分のアプリではログインの処理はFlutterとFirebaseで完結させたかったので実装してみました。
完成系はこんな感じです。(あくまで参考に)
Sign In with Appleはこんな感じ。
— shogo.yamada (@yshogo87) September 29, 2019
ユーザー体験が最高だから基本は導入したいけど、別プラットフォームで同じアカウントでログインしたい時どうしたらいいんだろう🤔 pic.twitter.com/cZXTQbOrGx
まずは準備
まずは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")
],
),
),
);
}
}
これでボタンが表示されます。下がボタンが表示された例です。
お、FlutterでSign in with Apple実装できそうだ😳 pic.twitter.com/Wj06Dmc8h7
— shogo.yamada (@yshogo87) September 27, 2019
もしかしたらこのレイアウトだと、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関連で必要な権限が設定されていない可能性があります。
下記のツイートで解決しますので、詳しくはこちらをどうぞ
Google App Engine Node.jsでGoogle Cloud StorageのSigned URLを取得する by @ogawa0071 https://t.co/EGPkuqMVsb
— shogo.yamada (@yshogo87) September 29, 2019
この方法で解決しました!
こんな権限いじらないといけないんか、、、
めっちゃハマった、、、 https://t.co/eOopZtOvpZ
今不明なこと
Firebase にカスタム認証したときにSign In with Appleでメールアドレスを公開しない設定にしてるときにサインアップするとFirebaseのコンソールでの表示がこういう風になってしまうんだけど、本当にこれでいいのかな。 pic.twitter.com/uc9m5A96xx
— shogo.yamada (@yshogo87) September 29, 2019
このツイートについてもし知ってる方いらっしゃれば、コメント欄にてご連絡ください。