-
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
Changes from all commits
16b9d2c
3d08099
55a4639
70b2625
e7bb2e1
e4b5919
327a8e8
b7d5039
4040f74
979b433
293e492
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,22 @@ | ||
import 'dart:convert'; | ||
|
||
import 'package:crypto/crypto.dart'; | ||
import 'package:firebase_auth/firebase_auth.dart'; | ||
import 'package:google_sign_in/google_sign_in.dart'; | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
import 'package:sign_in_with_apple/sign_in_with_apple.dart'; | ||
|
||
import '../user/worker.dart'; | ||
|
||
/// Firebase Console の Authentication で設定できるサインイン方法の種別。 | ||
enum SignInMethod { | ||
google, | ||
apple, | ||
line, | ||
// TODO: 後で削除する予定 | ||
email, | ||
; | ||
} | ||
|
||
/// [FirebaseAuth] のインスタンスを提供する [Provider]. | ||
final _authProvider = Provider<FirebaseAuth>((_) => FirebaseAuth.instance); | ||
|
@@ -25,14 +42,20 @@ final isSignedInProvider = Provider<bool>( | |
(ref) => ref.watch(userIdProvider) != null, | ||
); | ||
|
||
final authServiceProvider = | ||
Provider.autoDispose<AuthService>((_) => const AuthService()); | ||
final authServiceProvider = Provider.autoDispose<AuthService>((ref) { | ||
return AuthService( | ||
workerService: ref.watch(workerServiceProvider), | ||
); | ||
}); | ||
|
||
/// [FirebaseAuth] の認証関係の振る舞いを記述するモデル。 | ||
class AuthService { | ||
const AuthService(); | ||
const AuthService({ | ||
required WorkerService workerService, | ||
}) : _workerService = workerService; | ||
|
||
static final _auth = FirebaseAuth.instance; | ||
final WorkerService _workerService; | ||
|
||
// TODO: 開発中のみ使用する。リリース時には消すか、あとで デバッグモード or | ||
// 開発環境接続時のみ使用可能にする。 | ||
|
@@ -43,6 +66,77 @@ class AuthService { | |
}) => | ||
_auth.signInWithEmailAndPassword(email: email, password: password); | ||
|
||
/// [FirebaseAuth] に Google でサインインする。 | ||
/// https://firebase.flutter.dev/docs/auth/social/#google に従っている。 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FlutterFire の公式ドキュメントに従っていることを明記しました。 |
||
Future<UserCredential> signInWithGoogle() async { | ||
final googleUser = await GoogleSignIn().signIn(); // サインインダイアログの表示 | ||
final googleAuth = await googleUser?.authentication; // アカウントからトークン生成 | ||
final credential = GoogleAuthProvider.credential( | ||
accessToken: googleAuth?.accessToken, | ||
idToken: googleAuth?.idToken, | ||
); | ||
|
||
final userCredential = await _auth.signInWithCredential(credential); | ||
await _maybeCreateWorkerByUserCredential(userCredential: userCredential); | ||
return userCredential; | ||
} | ||
|
||
/// [FirebaseAuth] に Apple でサインインする。 | ||
/// https://firebase.flutter.dev/docs/auth/social/#apple に従っている。 | ||
Future<UserCredential> signInWithApple() async { | ||
final rawNonce = generateNonce(); | ||
final nonce = _sha256ofString(rawNonce); | ||
|
||
final appleCredential = await SignInWithApple.getAppleIDCredential( | ||
scopes: [ | ||
AppleIDAuthorizationScopes.email, | ||
AppleIDAuthorizationScopes.fullName, | ||
], | ||
nonce: nonce, | ||
); | ||
|
||
final oauthCredential = OAuthProvider('apple.com').credential( | ||
idToken: appleCredential.identityToken, | ||
rawNonce: rawNonce, | ||
); | ||
|
||
final userCredential = | ||
await FirebaseAuth.instance.signInWithCredential(oauthCredential); | ||
await _maybeCreateWorkerByUserCredential(userCredential: userCredential); | ||
return userCredential; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Android での Apple ログインのためだったのですね!🙏私も知りませんでした、勉強になりました! |
||
} | ||
|
||
/// 文字列から SHA-256 ハッシュを作成する。 | ||
String _sha256ofString(String input) { | ||
final bytes = utf8.encode(input); | ||
final digest = sha256.convert(bytes); | ||
return digest.toString(); | ||
} | ||
|
||
/// サインイン時に、まだ Worker ドキュメントが存在していなければ、Firebase | ||
/// の [UserCredential] をもとに生成する。 | ||
/// Google や Apple によるはじめてのログインのときに相当する。 | ||
Future<void> _maybeCreateWorkerByUserCredential({ | ||
required UserCredential userCredential, | ||
}) async { | ||
final user = userCredential.user; | ||
if (user == null) { | ||
// UserCredential | ||
return; | ||
} | ||
final workerExists = await _workerService.workerExists(workerId: user.uid); | ||
if (workerExists) { | ||
return; | ||
} | ||
await _workerService.createWorker( | ||
workerId: user.uid, | ||
displayName: user.displayName ?? '', | ||
); | ||
} | ||
|
||
/// [FirebaseAuth] からサインアウトする。 | ||
Future<void> signOut() => _auth.signOut(); | ||
Future<void> signOut() async { | ||
await _auth.signOut(); | ||
await GoogleSignIn().signOut(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import 'package:flutter/services.dart'; | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
|
||
import '../../../scaffold_messenger_controller.dart'; | ||
import '../auth.dart'; | ||
|
||
final authControllerProvider = Provider.autoDispose<AuthController>( | ||
(ref) => AuthController( | ||
authService: ref.watch(authServiceProvider), | ||
appScaffoldMessengerController: | ||
ref.watch(appScaffoldMessengerControllerProvider), | ||
), | ||
); | ||
|
||
class AuthController { | ||
const AuthController({ | ||
required AuthService authService, | ||
required AppScaffoldMessengerController appScaffoldMessengerController, | ||
}) : _authService = authService, | ||
_appScaffoldMessengerController = appScaffoldMessengerController; | ||
|
||
final AuthService _authService; | ||
final AppScaffoldMessengerController _appScaffoldMessengerController; | ||
|
||
/// 選択した [SignInMethod] でサインインする。 | ||
Future<void> signIn(SignInMethod authenticator) async { | ||
switch (authenticator) { | ||
case SignInMethod.google: | ||
try { | ||
await _authService.signInWithGoogle(); | ||
} | ||
// キャンセル時 | ||
on PlatformException catch (e) { | ||
if (e.code == 'network_error') { | ||
_appScaffoldMessengerController | ||
.showSnackBar('接続できませんでした。\nネットワーク状況を確認してください。'); | ||
} | ||
_appScaffoldMessengerController.showSnackBar('キャンセルしました。'); | ||
} | ||
|
||
case SignInMethod.apple: | ||
// Apple はキャンセルやネットワークエラーの判定ができないので、try-catchしない | ||
await _authService.signInWithApple(); | ||
case SignInMethod.line: | ||
case SignInMethod.email: | ||
throw UnimplementedError(); | ||
} | ||
return; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
import 'package:sign_in_button/sign_in_button.dart'; | ||
|
||
import '../auth.dart'; | ||
import 'auth_controller.dart'; | ||
|
||
class GoogleAppleSignin extends ConsumerWidget { | ||
const GoogleAppleSignin({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
return Scaffold( | ||
appBar: AppBar( | ||
title: const Text('サインインページ'), | ||
), | ||
body: Center( | ||
child: Column( | ||
children: [ | ||
SizedBox( | ||
height: 50, | ||
child: SignInButton( | ||
Buttons.google, | ||
text: 'Google でサインイン', | ||
onPressed: () async => ref | ||
.read(authControllerProvider) | ||
.signIn(SignInMethod.google), | ||
), | ||
), | ||
// Apple | ||
SizedBox( | ||
height: 50, | ||
child: SignInButton( | ||
Buttons.apple, | ||
text: 'Apple でサインイン', | ||
onPressed: () async => | ||
ref.read(authControllerProvider).signIn(SignInMethod.apple), | ||
), | ||
), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
} |
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」に名前を合わせました。