-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#45 GoogleとAppleでのログイン処理作成 #61
Conversation
SealedTimestamp createdAt = const ServerTimestamp(), | ||
SealedTimestamp updatedAt = const ServerTimestamp(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flutterfire_gen の内部のことになるので説明が足りておらずすみませんすが、この生成物:
のように、指定せずとも勝手に createdAt と updatedAt はフィールドに入る気がします!
一応確認してもらって大丈夫そうだったらそうしてください!(このファイルで flutterfire_json_converters.dart を import する必要もなくなりますね 👍)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kosukesaigusa
おっしゃる通り自動で値が入りますので削除しました!
|
||
|
||
/// [Worker] を作成する。 | ||
Future<void> set({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[軽微]
fetchWorker
に合わせて setWorker
が良さそうですね!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kosukesaigusa
ご指摘ありがとうございます!
修正しました!
|
||
static final _auth = FirebaseAuth.instance; | ||
final ProviderRef<dynamic> _ref; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Riverpod の使い方としてはとても正しいです!が、事前に読んでもらった
の規約に従って、ref
を丸ごと渡すのは禁止にしようと思っています!🙏(底に書いてあるとおり、冗長になって Riverpod の良さは少し失ってしまうのは分かりつつ、分かり易さをとって、AuthService
の中で使用するインスタンスはコンストラクタで直接渡してください)
例:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kosukesaigusa
修正しました。
後学のためにご存じであればご教示いただきたいのですが、refを受け取らずにAuthServiceクラス内からStateProviderの値を更新したり参照したりする方法はあるのでしょうか?
(いろいろ調べたんですが、わかりませんでした。)
isHost: isHost, | ||
); | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[軽微] ファイル末尾には空行入れるのをお願いします!(エディタでそういう設定ができると思います、ぜひしてください!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kosukesaigusa
すみません、設定していなかったです。。
修正しました!
|
||
/// OAuthを使用したサインイン | ||
Future<void> signInOauth(Authenticator authenticator) async { | ||
switch(authenticator){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[軽微(だけど必須で直したい)]
このあたり、通常の lint format がかかると switch (authenticator) {
という感じで空白が必ず入ると思いますが、エディタの設定はうまくいっていそうですか?(他の箇所でも多数そういうものがありそうです)
私を含む他のメンバーが command + S をすると差分が出てしまうので、直していただきたいです!🙏
もし困ったらサポートします!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kosukesaigusa
すみません、そんな設定があるなんて知りませんでした。
助かりました!修正しました!
final googleUser = await GoogleSignIn().signIn(); // サインインダイアログの表示 | ||
final googleAuth = await googleUser?.authentication; // アカウントからトークン生成 | ||
if(googleAuth != null){ | ||
await _authService.signInWithGoogle(googleAuth: googleAuth); | ||
return ; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この処理はコントローラではなくモデルに移すべきに思います。
コントローラでは単にユーザー操作から
- ユーザー操作;「A という認証プロバイダでログインしたいです」
- コントローラ:「モデルのそのメソッド呼びますね(必要ならエラーハンドリングして SnackBar を出したりもします)」
- モデル:「A という認証プロバイダでのログイン」という振る舞いをします
という感じの役割分担でリファクタできないでしょうか?
また、その際、モデルのコードは公式ドキュメント:
https://firebase.flutter.dev/docs/auth/social/#google
import 'package:google_sign_in/google_sign_in.dart';
Future<UserCredential> signInWithGoogle() async {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication? googleAuth = await googleUser?.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
// Once signed in, return the UserCredential
return await FirebaseAuth.instance.signInWithCredential(credential);
}
とほぼ一致するようになると思います!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// キャンセル時 | ||
on FirebaseAuthException catch(_){ | ||
// Appleはネットワークエラーとキャンセル時の判断ができないため、スナックバーは表示しない | ||
return ; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
何もしないならキャッチしなくていいと思います!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kosukesaigusa
削除しました!
), | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[軽微] ファイル末尾には空行入れるのをお願いします!(エディタでそういう設定ができると思います、ぜひしてください!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kosukesaigusa
修正しました!
final UserCredential userCredential; | ||
userCredential = await _auth.signInWithProvider(appleProvider); | ||
await _signIn(Authenticator.apple, userCredential); | ||
return userCredential; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この処理が公式ドキュメント
https://firebase.flutter.dev/docs/auth/social/#apple
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
/// Generates a cryptographically secure random nonce, to be included in a
/// credential request.
String generateNonce([int length = 32]) {
final charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)])
.join();
}
/// Returns the sha256 hash of [input] in hex notation.
String sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
Future<UserCredential> signInWithApple() async {
// To prevent replay attacks with the credential returned from Apple, we
// include a nonce in the credential request. When signing in with
// Firebase, the nonce in the id token returned by Apple, is expected to
// match the sha256 hash of `rawNonce`.
final rawNonce = generateNonce();
final nonce = sha256ofString(rawNonce);
// Request credential for the currently signed in Apple account.
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
// Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`, sign in will fail.
return await FirebaseAuth.instance.signInWithCredential(oauthCredential);
}
と一致していないのには理由がありますか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kosukesaigusa
勘違いでしたらすみません。。
Androidで試したときに例外が発生してできなかったので、調べていたところこちらのissueを見つけました。
上記Issueやほかのサイトなどを確認するとAndroidではこの方法が非対応みたいでしたので、別のログイン方法にしていました。
ただ、AndroidではAppleログインを行わないとのことでしたので、ソースはflutterfireを使用する方法に修正しました!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Android での Apple ログインのためだったのですね!🙏私も知りませんでした、勉強になりました!
今回は Android で Apple ログインはしない前提でいきましょう!変更ありがとうございます!!
Future<void> signOut() async { | ||
await _auth.signOut(); | ||
if(_ref.read(authenticatorProvider) == Authenticator.google){ | ||
await GoogleSignIn().signOut(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
公式ドキュメントなどで GoogleSignIn().signOut()
の必要性は言及されていそうですか?
されていない場合、実際にこれはどのくらい必要かしりたいです!
もしなくても良さそうなら、authenticatorProvider
自体が不要になるのでは?ともぱっと見ですが思っています
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kosukesaigusa
特に公式では言及されていませんでした。
ただ、この処理を実行しないとアプリケーションとアカウントの紐づけが解除されません。
そのため、
- Googleでログイン
- アプリのサインアウトボタン押下
- 再度Googleでログインボタン押下→アカウントを選ぶことなく即ログイン
という形になってしまいます。
1アカウントだけでしたら上記は便利ですが、別のGoogleアカウントでログインしたいとなった際に
切り替えができず不便かもしれないと思い追加していました。
・コードのフォーマット修正 ・ProviderRefをコンストラクタに渡さないように修正 ・Appleのログイン方法をflutterFireを使用するやり方に変更 ・Googleのログイン処理をすべてServiceに移動
@kosukesaigusa |
enum Authenticator { | ||
none, | ||
/// Firebase Console の Authentication で設定できるサインイン方法の種別。 | ||
enum SignInMethod { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Firebase Console の表記「Sign-in method」に名前を合わせました。
/// そのため、以下のような条件で判定を行う。 | ||
/// [UserInfo.providerId] : 'google.com' => google認証 | ||
/// [UserInfo.providerId] : 'apple.com' => apple認証 | ||
final authenticatorProvider = Provider<Authenticator>((ref) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Google で明示的にサインアウトするために使用していると思いますが、後述の方法で、どの SignInMethod
でサインインしたかの記録は特にしない方針に(今のところは)しようと思ったので消しました!(他に必要性が出てきたら復活見当します)
@@ -96,7 +66,8 @@ class AuthService { | |||
}) => | |||
_auth.signInWithEmailAndPassword(email: email, password: password); | |||
|
|||
/// Googleでのサインイン | |||
/// [FirebaseAuth] に Google でサインインする。 | |||
/// https://firebase.flutter.dev/docs/auth/social/#google に従っている。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FlutterFire の公式ドキュメントに従っていることを明記しました。
// ignore: avoid_catches_without_on_clauses | ||
} catch (_) { | ||
// TODO: Google でサインインしていない場合に上記の GoogleSignIn().signOut(); | ||
// をコールして例外やエラーが発生しうるか調べる。 | ||
// もし発生しないならこの try catch 句の記述を省略する。 | ||
// もし発生するなら、この TODO コメントを破棄して、代わりに意図的にそこで | ||
// 発生する例外やエラーを握り潰していることをコメントで明記する。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RikitoNoto
ここにコメントを書いた内容の対応をしていただきたいです!🙏
簡単に言うと、
Google でログインしていないのに GoogleSignIn().signOut()
をした場合に
- 例外やエラーが出る → 握りつぶす
- 例外やエラーが出る → 特に何もしない
でいいんじゃないかなと思っています!
|
||
/// サインイン中のユーザーに対応するワーカードキュメントが存在するかどうか判定する | ||
/// `Future<bool> Function()` 型の関数を提供する [Provider]. | ||
final workerDocumentExistsProvider = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
既存の host で似たような実装(それが理想的かは 100% 自身はないです)があったので真似たのだと思うのですが、そちらは main の処理で、ホストドキュメントの存在を確認して、ProviderScope.overrides で UserModeStateProvider
の初期値を決めるための実装でした(つまり ref が必要で、トップレベルのメソッドして定義したかった)。
今回は普通に WorkerRepository にそのようなメソッドをはやしました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
せっかく書いてもらいましたが、FlutterFire の公式の方法にただ従っていること、private メソッドにすれば(その private メソッドを使用するテストが書かれていれば)テストを本来書かなくていいこと(書いていないけど)、などから消しました。
本当にやるとしたら、AuhService の static メソッドではなく、visibleForTesting な private メソッド化、シンプルに lib 直下の適切なファイルにトップレベルのメソッドして配置してやるのが自然かもなとも思います!(public な static メソッドにすると、他のクラスからも呼ぶことがあるのかないのか不明確になるなどのデメリットがあるなと私は思っています)
Issue
close #45
説明
GoogleとAppleアカウントを使用してログインを行う処理を作成しました。
作成した処理
UI
Googleでのログイン
Appleでのログイン
その他
ログインユーザーのuidをドキュメントIDとしてworkerを作成しています。
そのためアカウントごとにユーザ作成となります。
(複数アカウントで一つのworkerの場合はどのように紐づけをするか、仕様の検討が必要かと思いますので相談させてください。)
チェックリスト
PR の冒頭に関連する Issue 番号を記載しましたか?
本 PR の変更に関して、エディタや IDE で意図しない警告は増えていませんか?(lint 警告やタイポなど)
今回対応の部分でlint警告はありませんでした。
Issue の完了の定義は満たせていますか?