4月は3週間の海外旅行に行っていたので久々の更新。
FlutterでFirebase Authenticationを実装してみたので記載。今回は純粋なアプリのログインのみ実装。 次回の記事でbackendの接続とアプリの状態保持を記載する予定。
PR
使用パッケージ
- firebase_auth | Flutter package
- google_sign_in | Flutter package
- 今回はGoogleログインのみ実装したので使用
- flutter_secure_storage | Flutter package
実装
まず、Firebase接続の事前準備が必要なので以下の記事を参考にinfo. plist
を追加 & 修正
次に以下のコマンドでFirebaseの接続情報を追加
$ firebase login $ dart pub global activate flutterfire_cli
接続したいFirebaseのプロジェクトを指定すると以下のファイルが追加される(接続情報が載っているのでGitのcommitからは除外)
■ lib/firebase_options.dart
// File generated by FlutterFire CLI. // ignore_for_file: type=lint import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; /// Default [FirebaseOptions] for use with your Firebase apps. /// /// Example: /// ```dart /// import 'firebase_options.dart'; /// // ... /// await Firebase.initializeApp( /// options: DefaultFirebaseOptions.currentPlatform, /// ); /// ``` class DefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) { return web; } switch (defaultTargetPlatform) { case TargetPlatform.android: return android; case TargetPlatform.iOS: return ios; case TargetPlatform.macOS: return macos; case TargetPlatform.windows: return windows; case TargetPlatform.linux: throw UnsupportedError( 'DefaultFirebaseOptions have not been configured for linux - ' 'you can reconfigure this by running the FlutterFire CLI again.', ); default: throw UnsupportedError( 'DefaultFirebaseOptions are not supported for this platform.', ); } } static const FirebaseOptions web = FirebaseOptions( apiKey: '******'', appId: '******'', messagingSenderId: '******'', projectId: '******', authDomain: '******', storageBucket: '******', measurementId: '******', ); (略)
上記のコードを使用してアプリ起動時ににFirebaseの初期化の処理を追加
import 'firebase_options.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); await initHiveForFlutter(); runApp(const MyApp()); }
これで準備完了なので残りはコードを追加
まずログイン画面の実装
import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:sampleflutter/components/appBar/common.dart'; import 'package:sampleflutter/components/button/button.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:sampleflutter/utils/auth.dart'; class Login extends HookWidget { const Login({Key? key}) : super(key: key); static final googleLogin = GoogleSignIn(scopes: [ 'email', 'https://www.googleapis.com/auth/contacts.readonly', ]); @override Widget build(BuildContext context) { onPressed() async { try { //Google認証フローを起動する final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn(); //リクエストから認証情報を取得する final googleAuth = await googleUser?.authentication; //firebaseAuthで認証を行う為、credentialを作成 final credential = GoogleAuthProvider.credential( accessToken: googleAuth?.accessToken, idToken: googleAuth?.idToken, ); //作成したcredentialを元にfirebaseAuthで認証を行う UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(credential); if (userCredential.additionalUserInfo!.isNewUser) { //新規ユーザーの場合の処理 debugPrint("新規ユーザー"); } else { //既存ユーザーの場合の処理 debugPrint("既存ユーザー"); } final AuthService authService = AuthService(); await authService.refreshAndStoreToken(); } on FirebaseException catch (e) { debugPrint(e.message); } catch (e) { print(e); } } onLogout() async { final AuthService authService = AuthService(); await authService.deleteToken(); await FirebaseAuth.instance.signOut(); } return Scaffold( appBar: const CommonAppBar(title: "ログイン"), body: Center( child: StreamBuilder( stream: FirebaseAuth.instance.authStateChanges(), builder: (BuildContext context, AsyncSnapshot<User?> snapshot) { if (!snapshot.hasData) { return Button( title: "Google ログイン", width: 300, onPressed: onPressed); } else { return Button( title: "ログアウト", width: 300, onPressed: onLogout); } }))); } }
これで以下みたいにログインの実装が可能
後はGraphQLに認証トークンを追加する場合は以下にように実装 まず、Auth用のServiceを追加
import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class AuthService { final FirebaseAuth _auth = FirebaseAuth.instance; final FlutterSecureStorage secureStorage = const FlutterSecureStorage(); // トークンを取得して安全に保存する Future<void> refreshAndStoreToken() async { User? user = _auth.currentUser; if (user != null) { String? token = await user.getIdToken(true); await secureStorage.write(key: 'token', value: token); await secureStorage.write( key: 'tokenDate', value: DateTime.now().toIso8601String()); } } // トークンの有効性をチェックし、必要に応じて更新 Future<String?> getToken() async { try { String? token = await secureStorage.read(key: 'token'); if (token == null) { return null; } String? storedDate = await secureStorage.read(key: 'tokenDate'); if (storedDate == null) { return null; } DateTime tokenDate = DateTime.parse(storedDate); DateTime now = DateTime.now(); // トークンの有効期限を設定(Firebaseのデフォルトは約1時間) if (now.difference(tokenDate).inHours >= 1) { await refreshAndStoreToken(); token = await secureStorage.read(key: 'token'); // 更新されたトークンを取得 } debugPrint('token: $token'); return token; } catch (e) { return null; } } Future<void> deleteToken() async { await secureStorage.delete(key: 'token'); await secureStorage.delete(key: 'tokenDate'); } }
上記で追加したgetToken
を以下のメソッドに追加してHeaderに追加
class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { final HttpLink httpLink = HttpLink('https://stock-keeper-voytob3xvq-an.a.run.app/graphql'); final AuthService authService = AuthService(); final authLink = AuthLink(getToken: () async => authService.getToken()); final link = authLink.concat(httpLink); final ValueNotifier<GraphQLClient> client = ValueNotifier<GraphQLClient>( GraphQLClient( link: link, cache: GraphQLCache(store: InMemoryStore()), ), );
これでAPIでFirebase Authを使用する準備が整った。次回の記事でbackendの接続とアプリの状態保持を記載する予定。