【Flutter】Google認証（ログイン／サインイン）の実装

## 1. はじめに

Googleアカウントを利用したログインは、ユーザーにとって簡単でセキュアな方法です。多くのユーザーが既にGoogleアカウントを持っているため、Googleでのサインインはアプリへのスムーズなアクセスを提供し、ユーザーエクスペリエンスを向上させます。

Flutterでは、FirebaseというGoogleが提供するサーバーサイドのモバイルアプリ開発用プラットフォームを使用するのが定石です。Firebaseを利用する事で、Googleアカウントでのログインをセキュアかつシンプルに実装できます。

本記事では、Flutterを使用してFirebase Authenticationを利用したGoogleアカウントでのログインを実装する手順を詳しく解説します。

## 2. 事前準備

### 1. Firebaseの設定
**1.1  [Firebase Console](https://console.firebase.google.com/)でプロジェクトを作成します。**

**1.2  FirebaseプロジェクトにFlutterを追加します。**

初回のみ、以下の手順が必要です。
- [Firebase CLI](https://firebase.google.com/docs/cli?hl=ja&authuser=0&_gl=1*1kqwx7b*_ga*MTYyNDE2OTcyNS4xNzI1NjIxMDQx*_ga_CW55HF8NVT*MTcyODcyNDQ2NC4yNC4xLjE3Mjg3MjYwNjkuNjAuMC4w#install_the_firebase_cli) をPCにインストールしてログインする
```bash
firebase login
```
- 任意のディレクトリから次のコマンドを実行する
```bash
dart pub global activate flutterfire_cli
```
初回・2回目以降、以下の手順が必要です。
- Flutterで新規プロジェクトを作成する
- Flutterプロジェクトのルートで次のコマンドを実行する
```bash
flutterfire configure --project=<Firebase Project Name>
```
- 「構築」> 「Authentication」に移動し、ログイン方法としてGoogleを有効化します。

**1.3  Android固有の設定**
Android端末でGoogle認証を行う場合、SHA1証明書フィンガープリントが必要になります。デバッグ用／リリース用でそれぞれ作成する必要があります。

**デバッグ用（今回はこちらを利用）**
Flutterプロジェクトのルートで下記のコマンドを実行し、パスワードに「android」と入力します。画面に表示されるSHA1をコピーします。

```bash
keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore
```
**リリース用**
アプリのリリース時、`Google Play Console`にアプリをアップロードする手順がありますが、SHA1証明書フィンガープリントは`Google Play Console`から取得することができます。対象アプリを選択し、「設定」>「アプリの完全性」>「アプリ署名鍵の証明書」からコピーします。

**Firebaseにフィンガープリントを設定**

Firebaseの「プロジェクトの設定」画面でAndroidアプリのフィンガープリントに設定します。その後、`google-service.json`をダウンロードし、Flutterプロジェクトの`android/app/google-service.json`と置き換えます。

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/0ec7be33-e041-ac1c-0309-a1cb0e77488a.png)

**1.4  iOS固有の設定**
iOS端末でGoogle認証を行う場合、Firebaseの「プロジェクトの設定」画面で`GoogleService-Info.plist`をダウンロード後、Xcodeを起動し「Runner」を右クリックして「Add Files to "Runner"」を選択し、上記ファイルを選択します。

![あああ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/ce71a606-545a-db5f-2497-7064842c2663.png)

![スクリーンショット 2024-10-12 21.42.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/ebe434c0-b403-d130-6748-b4707829636b.png)

![あああ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674933/7db94e5b-09f7-8d28-f603-1340a53db53f.png)

Flutterプロジェクトの`ios/Runner/info.plist`の`<dict>ブロック`の末尾にCFBundleURLTypesを追記します。CFBundleURLSchemesには、「GoogleService-Info.plist」に記載されているREVERSED_CLIENT_IDを設定します。

```diff
<plist>
<dict>

     // 末尾に追記
+    <key>CFBundleURLTypes</key>
+    <array>
+        <dict>
+            <key>CFBundleTypeRole</key>
+            <string>Editor</string>
+            <key>CFBundleURLSchemes</key>
+            <array>
+                <string>{REVERSED_CLIENT_ID}</string>
+            </array>
+        </dict>
+    </array>
</dict>
</plist>

```

### 2. **パッケージのインストール**

`firebase_core`パッケージをプロジェクトに追加します。
Firebase全般を利用する際に必要です。

```bash
flutter pub add firebase_core
```

`firebase_auth`パッケージをプロジェクトに追加します。
Firebase Authenticationを利用する際に必要です。

```bash
flutter pub add firebase_auth
```
`google_sign_in`パッケージをプロジェクトに追加します。
Googleアカウントでの認証を利用する際に必要です。

```bash
flutter pub add google_sign_in
```

## 3. 実装

`google_sign_in`と`firebase_auth`を組み合わせてGoogleログインを実装します。

```dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Firebaseの初期化
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SignInDemo(),
    );
  }
}

class SignInDemo extends StatefulWidget {
  @override
  _SignInDemoState createState() => _SignInDemoState();
}

class _SignInDemoState extends State<SignInDemo> {
  // FirebaseAuthインスタンスを取得
  final FirebaseAuth _auth = FirebaseAuth.instance;
  // GoogleSignInインスタンスを取得
  // scopeにはAPIを通して操作できるユーザのリソースを指定する、以下のページを参照
  // https://developers.google.com/identity/protocols/oauth2/scopes?hl=ja#fcm
  final GoogleSignIn _googleSignIn = GoogleSignIn(scopes: [
    // 例えば、Google Calendarの情報を操作するには、ここに範囲を記載する
    // https://www.googleapis.com/auth/calendar.readonly,
    // https://www.googleapis.com/auth/calendar.events,
  ]);

  // ログインしたユーザー情報を保持する変数
  User? _user;

  // Googleサインインメソッド
  Future<User?> signInWithGoogle() async {
    try {
      // Googleサインインを実行
      final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
      if (googleUser == null) {
        // キャンセルされた場合はnullを返す
        return null;
      }

      // Googleの認証情報を取得
      final GoogleSignInAuthentication googleAuth = await googleUser.authentication;

      // Firebase用の資格情報を作成
      final AuthCredential credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      // Firebaseに認証情報を登録
      final UserCredential userCredential = await _auth.signInWithCredential(credential);
      final User? user = userCredential.user;

      setState(() {
        // ログインしたユーザー情報を取得し画面更新
        _user = user;
      });
      return user;

    } catch (e) {
      print("Error during Google Sign In: $e");
      return null;
    }
  }

  // サインアウトメソッド
  Future<void> signOut() async {
    await _googleSignIn.signOut();
    await _auth.signOut();
    setState(() {
      _user = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firebase Google Sign In'),
      ),
      body: Center(
        child: _user == null
            ? ElevatedButton(
                onPressed: signInWithGoogle,
                child: Text('Sign in with Google'),
              )
            : Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text('Signed in as ${_user!.displayName}'),
                  Text('Email: ${_user!.email}'),
                  _user!.photoURL != null
                      ? Image.network(_user!.photoURL!)
                      : Container(),
                  SizedBox(height: 20),
                  ElevatedButton(
                    onPressed: signOut,
                    child: Text('Sign out'),
                  ),
                ],
              ),
      ),
    );
  }
}

```

### コードの説明

1. **Firebase Authentication初期化**
   - `FirebaseAuth.instance`で認証サービスを初期化します。
   
2. **Googleサインインの処理**
   - `GoogleSignIn().signIn()`でGoogleアカウントの選択を行います。
   - `GoogleSignInAuthentication`を使って、`accessToken`と`idToken`を取得します。
   - 取得した認証情報を使って、`signInWithCredential`メソッドでFirebaseにサインインします。

3. **Googleサインアウトの処理**
   - `googleSignIn.signOut()`と`_auth.signOut()`を呼び出すことで、GoogleとFirebaseの両方からサインアウトします。

### ログイン後の情報取得

ユーザーがログインした後は、`_user`からGoogleアカウントの基本情報（`displayName`, `email`, `photoURL`など）にアクセスできます。

```dart
Text('Signed in as ${_user!.displayName}'),
Text('Email: ${_user!.email}'),
Image.network(_user!.photoURL ?? ''),
```
### 実行方法
アプリを実行し、Sign in with Googleボタンを押すとGoogleアカウントにログインし、Sign outボタンを押すとログアウトできます。

## 4. Firebase Consoleでログイン情報の確認

Firebase Consoleの「Authentication」で、実際にログインしたユーザーの情報を確認することができます。

## 5. おわりに

この方法で、FlutterアプリにFirebase Authenticationを使ったGoogleログインを簡単に組み込むことができます。