Github Auth

OAuth

これをやりたかったのである。
コールバック先として本番環境のホスト名が必要になる予感がしたので、昨日までDigitalOceanで仮想マシン作ったり、ついでにGithubActionsでCI化したり、FirebaseHostingに配置したりしてたのである。
しかしながら実際やってみたらコールバック先はFirebaseに集約されるっぽいので先回りする必要はなかったのかもしれない。

FirebaseとGithubの設定

  1. FirebaseのコンソールからAuthentication->Sign-in methodを選択して一覧からGithubを選択。

  2. 認証コールバックURLをコピーしておく

  3. Githubの設定->Developer Settings->OAuth Appsを選択してRegister a new aplicationボタンを押下。

  4. Generate a new lient secretボタンを押下

  5. ClientIDClientSecretをコピーしておく

  6. Authorization callback URLに手順2でコピーしたURLを貼り付け
    ※その他は適当に入力する
    ※ダークモードになってるのはSS撮り忘れてた為

  7. Firebaseコンソールに戻って手順5でコピーしたClientIDClientSecretを貼り付け

  8. アプリからログインするとユーザ一覧に追加されることが確認できる。

Flutterクライアント(Web)の修正

  1. firebaseコンソールから追加したWebアプリを選択し、CDNタブに表示されるタグをコピー

  2. ./web/index.htmlにfirebaseに必要なスクリプトを追加

<body>
  <!-- ★ここから -->

  <!-- The core Firebase JS SDK is always required and must be listed first -->
  <script src="https://www.gstatic.com/firebasejs/8.4.3/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/8.4.3/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/8.4.3/firebase-firestore.js"></script>
  <!-- TODO: Add SDKs for Firebase products that you want to use
      https://firebase.google.com/docs/web/setup#available-libraries -->
  <script src="https://www.gstatic.com/firebasejs/8.4.3/firebase-analytics.js"></script>

  <script>
    // Your web app's Firebase configuration
    // For Firebase JS SDK v7.20.0 and later, measurementId is optional
    var firebaseConfig = {
      apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      authDomain: "XXXXXXXXXXX.firebaseapp.com",
      projectId: "XXXXXXXXXX",
      storageBucket: "XXXXXXXXXXX",
      messagingSenderId: "XXXXXXX",
      appId: "1:XXXXXXXXXXXXX:web:XXXXXXXXXX",
      measurementId: "G-XXXXXX"
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
    firebase.analytics();
  </script>
  <!-- ★ここまで -->

  <!-- This script installs service_worker.js to provide PWA functionality to
       application. For more information, see:
       https://developers.google.com/web/fundamentals/primers/service-workers -->
  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('flutter-first-frame', function () {
        navigator.serviceWorker.register('flutter_service_worker.js');
      });
    }
  </script>
  <script src="main.dart.js" type="application/javascript"></script>
</body>
  1. pubspec.ymlに以下を追加
firebase_auth: ^1.1.2
  1. 認証処理を実装
    flutterのGoogle認証のサンプルを参考に実装
import 'dart:async';

import 'package:clientapp/resources/authentication_repository/authentication_repository.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;

class SignUpFailure implements Exception {}

class LogInWithEmailAndPasswordFailure implements Exception {}

class LogInWithGithubFailure implements Exception {}

class LogOutFailure implements Exception {}

class AuthenticationRepository {
  AuthenticationRepository({
    CacheClient? cache,
    firebase_auth.FirebaseAuth? firebaseAuth,
  })  : _cache = cache ?? CacheClient(),
        _firebaseAuth = firebaseAuth ?? firebase_auth.FirebaseAuth.instance;

  final CacheClient _cache;
  final firebase_auth.FirebaseAuth _firebaseAuth;

  static const userCacheKey = '__user_cache_key__';

  Stream<User> get user {
    return _firebaseAuth.authStateChanges().map((firebaseUser) {
      final user = firebaseUser == null ? User.anonymous : firebaseUser.toUser;
      _cache.write(key: userCacheKey, value: user);
      return user;
    });
  }

  /// Returns the current cached user.
  /// Defaults to [User.anonymous] if there is no cached user.
  User get currentUser {
    return _cache.read<User>(key: userCacheKey) ?? User.anonymous;
  }

  Future<void> signUp({required String email, required String password}) async {
    try {
      await _firebaseAuth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on Exception {
      throw SignUpFailure();
    }
  }

  Future<void> logInWithEmailAndPassword({
    required String email,
    required String password,
  }) async {
    try {
      await _firebaseAuth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on Exception {
      throw LogInWithEmailAndPasswordFailure();
    }
  }

  Future<void> logInWithGithub() async {
    try {
      final provider = firebase_auth.GithubAuthProvider();
      // provider.addScope('repo');
      provider.setCustomParameters({
        'allow_signup': 'false',
      });
      await _firebaseAuth.signInWithPopup(provider);
    } on Exception {
      throw LogInWithGithubFailure();
    }
  }

  Future<void> logOut() async {
    try {
      await Future.wait([
        _firebaseAuth.signOut(),
        // _githubSignIn.signOut(),
      ]);
    } on Exception {
      throw LogOutFailure();
    }
  }
}

extension on firebase_auth.User {
  User get toUser {
    return User(id: uid, email: email, name: displayName, photo: photoURL);
  }
}

おじさんの感想

Githubアカウント持ってる一般の方は少ない気がするのでGoogleとかも追加しておくべき。
サーバ側はどう絡むのかと言うと、クライアントが取得したUIDを受け取ってFirebaseに問い合わせてユーザ情報を得るっぽい。
Firebaseって必須の知見なのでは?(2回目)

ちなみにローカルDebug環境でも認証できたので先に本番サーバを用意する必要は全然なかったです。