この記事はFlutter #2 Advent Calendar 2020の6日目の記事です。
アドベントカレンダーの参加は今年が初めてです。
いつもは個人ブログで記事を投稿していますので、Qiitaへの投稿は久しぶりです。
Flutterはクロスプラットフォーム開発ができる便利なツールですが、まだまだ実務で導入するには見えていない部分があり会社で導入するためにはハードルがあったりします。
例えば、どれだけOS依存の課題に対応できるか、そしてどれだけサーバー連携が絡んだ時に柔軟にカスタムできるかだったりですね。
今回はその企業でも導入する際に検討事項に入りそうな「アカウント周りの情報」にういて深堀りしてみます。
企業としては「ただユーザーがSNSアカウントでログインできたら良いや」というのは絶対ありえなく、ログインしたらユーザー情報を社内のデータベースに保存してセキュアに取り扱いたいというのが本音です。
また絶対にユーザー情報が外部に漏れても駄目ですね。
そんなアカウント周りの情報ですが、Flutterでのログイン機構だとまだまだ見えていない部分の方が多いです。
そこで、今回はAppleIDを使って認証するシステムであるSign in with Apple
における振る舞いについて見ていきます。
iOSエンジニアがFlutterでSign in with Apple
Flutterを導入できるプロジェクトの場合はだいたいは相性がいいFirebaseも導入してそこら辺はFirebaseが担ってくれる場面が多いのですが、プロジェクトによってはFirebase Authentication
の機能が使えずFirebase Authentication
で連携できない場面もあるかもしれません。
その場合にはFlutterでSign in with Appleでの認証が可能かどうかやっぱり気になります。
なので、今回はFirebase Authentication
が使えない場合を想定してみました。
今回はFlutterでのSign in with Apple認証をするために使うパッケージにsign_in_with_apple
をチョイスしてみます。非常にpopularなパッケージになっています。
sign_in_with_apple
URL: https://pub.dev/packages/sign_in_with_apple
このパッケージを使ってFlutterでSign in with Appleを実装してみます。
開発環境
Flutter 1.22.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 1aafb3a8b9 (2 weeks ago) • 2020-11-13 09:59:28 -0800
Engine • revision 2c956a31c0
Tools • Dart 2.10.4
- Xcode 12.1
- Android Studio 4.0
- iOS 14.3 (Sign in with Apple は実機で開発した方がスムーズのため)
発生したエラー
Xcodeのバージョンが古い
実機のiOSのバージョンに対してXcodeのバージョンが足りなかった時に発生したエラー。
═══════════════════════════════════════════════════════════════════════════════════
Error launching app. Try launching from within Xcode via:
open ios/Runner.xcworkspace
Your Xcode version may be too old for your iOS version.
═══════════════════════════════════════════════════════════════════════════════════
2020-11-29 18:23:16.711 ios-deploy[2686:17758351] [ !! ] Error 0xe8000022: The service is invalid. AMDeviceSecureStartService(device, serviceName, NULL, &dbgServiceConnection)
Could not run build/ios/iphoneos/Runner.app on 00008030-000508D21A83802E.
Try launching Xcode and selecting "Product > Run" to fix the problem:
open ios/Runner.xcworkspace
Error launching application on XXXXX.
おそらくXcodeのバージョンを上げたらいいのかと思いバージョンを上げてみる。
Xcode 12.2 でアプリが起動してくれました🎉
Flutter で Sign in with Apple の実装まで
それではFlutterでの本実装の解説に入ります。
Xcode側で「Capability」の設定を行う
Xcode側でSign in with Appleの設定を活性化させておきます。
これを設定していないと、Apple認証が正しく動作しません。
pubspec.yamlのソースコード
sign_in_with_appleをインストールするためpubspec.yamlファイルを編集します。
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.0
sign_in_with_apple: ^2.5.4 # 追加する
これでPub get
してインポートします。
ソースコードについて
今回は味気ないですが、単純に初期コードにSign in with Appleのボタンを追加するだけにします。
import 'package:flutter/material.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
SignInWithAppleButton(
onPressed: () async {
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
print(credential);
},
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Sampleにある通りにSign in with AppleのウィジェットはSignInWithAppleButton
だそうです。
これを使って実装しました。
これでソースコードをビルドすると次のような画面が表示されます。
個人的にボタンのデザインをカスタムにできたら嬉しいなと思っています。ま、多分カスタマイズできると思っています。黒色の「Sign in with Apple」をタップするとApple認証のやつが下から表示されます。
Apple認証はAppleのサーバーにリクエストしてレスポンスとしてUser情報を受け取りますので、非同期処理のためにasync/await
で対応します。
getAppleIDCredential
を叩いた時にscopes
引数があるのはUserのAppleIdに紐付いている
- 姓名
- メールアドレス
は任意でリクエストを送らないと取得できないようになっているからです。
ですので、名前とメールアドレスの取得が必要であればこのscopes
にAppleIDAuthorizationScopes.email
とAppleIDAuthorizationScopes.fullName
をセットしないといけません。
受け取れるユーザー情報
で、ここから本題になりますが、この受け取ったUser情報がどれくらいネイティブアプリと比べて取得できるのかを調べます。
今回のソースコードでは、
print(credential);
この部分の調査になります。まず、credentialはgetAppleIDCredential
を叩いたときに返ってくるものです。このメソッドをググると、
static Future<AuthorizationCredentialAppleID> getAppleIDCredential({
@required List<AppleIDAuthorizationScopes> scopes,
/// Optional parameters for web-based authentication flows on non-Apple platforms
///
/// This parameter is required on Android.
WebAuthenticationOptions webAuthenticationOptions,
/// Optional string which, if set, will be be embedded in the resulting `identityToken` field on the [AuthorizationCredentialAppleID].
///
/// This can be used to mitigate replay attacks by using a unique argument per sign-in attempt.
///
/// Can be `null`, in which case no nonce will be passed to the request.
String nonce,
/// Data that’s returned to you unmodified in the corresponding [AuthorizationCredentialAppleID.state] after a successful authentication.
///
/// Can be `null`, in which case no state will be passed to the request.
String state,
}) async {
というふうにFuture<AuthorizationCredentialAppleID>
が返ります。AuthorizationCredentialAppleID
はSign in with Appleを実装したiOSエンジニアならご存知ですがこれがApple認証が成功した時に受け取れるUser情報になります。
以下がcredential
の情報になります。
プロパティ | 役割 |
---|---|
userIdentifier | 一番重要なApple認証後のUser情報 |
givenName | 名 |
familyName | 性 |
メールアドレス | |
authorizationCode | 実はよくわかりません |
identityToken | JSON Web Token (JWTです、後述します) |
state | 状態 (よくわかりません) |
print(credential.userIdentifier);
print(credential.givenName);
print(credential.familyName);
print(credential.email);
print(credential.authorizationCode);
print(credential.identityToken);
print(credential.state);
ちなみに2回目以降のApple認証で取得できるUser情報はこちらになります。
givenName、familyName、emailが2回目以降取得できないのが再現されています。(それはそう。)
これら3つの情報を何度も取得したい場合は端末のApple認証ステータスをログアウトする必要があります。
「設定アプリ」から「パスワードとセキュリティ」「Apple IDを使用中のApp」の項目へ進んで「Apple IDの使用を停止する」を選択すればAppleIDの使用が停止され再度上記3つのデータを取得できるようになっているはずです。
identityToken の説明
そして、ここからはいつものSign in with Apple
の使い方ですが、identityToken
というのはJWTというもので、これは暗号化された文字列になっています。
この情報を解析するためには、
へアクセスして、
の「Encoded」の部分にidentityToken
をそのままコピーペーストすればデコードされた情報が確認できます。
そして、デコードされた情報の中にcredential.userIdentifier
と同じ情報が含まれています。
そのため、例えば、独自のAPIリクエストを使って社内のデータベースと認証して同じユーザーかどうかを確認する場合はこのuserIdentifier
を使えば良さそうです。
そんな感じでSwift/iOSでアレだけ面倒だったSign in with Appleがなんと
SignInWithAppleButton(
onPressed: () async {
final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
print(credential);
},
)
とこれだけでSign in with Appleの実装ができるのですね。
ここの部分をSwiftで書くとしたら下のようになります。
@objc
func handleAuthorizationAppleIDButtonPress() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
しかも動作や受け取れるユーザー情報もネイティブのときと同じです。
ただ、ちょっと気になるのがXcode 12.2じゃないとビルドできなかった点ぐらいでしょうか。
iOSが最新バージョンだった影響もあるかもしれません。
(なので、既存のプロジェクトでSign in with Appleを導入する場合は、OSのバージョンに注意したほうが良いかもしれません。)
ネイティブ実装
それではおまけ程度にネイティブでSign in with Appleを実装する方法を解説します。
とはいうもののそんなたいそうな話ではなく既にAppleがサンプルのアプリを用意してくれています。
Implementing User Authentication with Sign in with Apple
ここの「Download」からサンプルプロジェクトをダウンロードできます。
それを見て実装方法を調査すればできます。
ネイティブの場合はリクエストを送信するとDelegateでコールバックでUser情報が返ってきます。
細かいハマリポイントは会社のテックブログでまとめましたので良かったらこちらのページから確認してください。
iOS 版レアジョブアプリが Sign in with Apple に対応した話
こちらの記事では、本記事で取り上げなかった
- メールアドレスの取り扱い(メールを非公開、にした場合に得られるApple側のアドレスの内容)
- メール送信機能がある場合の対応方法
- iOS 13未満のOSに対する取り扱い
について解説しています。
ということで僕からは以上になります。