Open8

【Flutter】Google APIsで始めるOAuth2.0 ⭐️

heyhey1028heyhey1028

Overview

OAuth2.0に準拠した認証認可をFlutterで実現したい。
サンプルとして、以下処理について調査

  • Google Photoへのアクセス権限を認証し実行
  • リフレッシュトークンを取得した上で、アクセストークンの更新処理を実装
  • そのアクセストークン、リフレッシュトークンを使って、Cloud Functionsからも実行できるように実装

Architecture

Infrastracture: Firebase Firestore
BE: Cloud Functions
Client: Flutter

Approach

  • まずgoogle_sign_in パッケージを使ったGoogleサインイン
  • ただ調べたところ、リフレッシュトークンの発行は未対応の様子
  • 念の為、内部実装を確認し、リフレッシュトークンの発行処理を追加できるのかも検討
  • 最悪、シンプルにAPIをHTTP通信で叩いてGoogleサインイン等々を実装する

Dependencies

https://pub.dev/packages/google_sign_in
https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in
https://pub.dev/packages/extension_google_sign_in_as_googleapis_auth

heyhey1028heyhey1028

goole_sign_in packageを使ったGoogle SignIn

/// Holds authentication data after sign in.
class GoogleSignInTokenData {
  /// Build `GoogleSignInTokenData`.
  GoogleSignInTokenData({
    this.idToken,
    this.accessToken,
    this.serverAuthCode,
  });

  /// An OpenID Connect ID token for the authenticated user.
  String? idToken;

  /// The OAuth2 access token used to access Google services.
  String? accessToken;

  /// Server auth code used to access Google Login
  String? serverAuthCode;

手順

1. OAuth 2.0クライアントとしてアプリを登録

  • OAuthクライアントIDの作成

iOS

バンドルIDのみ必須。

登録後、以下の通り、OAuthクライアントIDが発行される。

Android

パッケージ名SHA-1 署名証明書フィンガープリントが必須。

2. 各プラットフォーム用の設定を行う

iOS

以下をInfo.plistに追加

Info.plist
<key>CFBundleURLTypes</key>
<array>
    <dict>
	<key>CFBundleTypeRole</key>
	<string>Editor</string>
	<key>CFBundleURLSchemes</key>
	<array>
	    <string>[ここにクライアントIDを反転させたモノを記載]</string>
	</array>
    </dict>
</array>

Android

android/build.graadleを以下の通り変更。

android/build.gradle
buildscript {
    ext.kotlin_version = '1.7.10'
    repositories {
        google()
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:7.3.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // added for google sign in
+      classpath 'com.google.gms:google-services:4.3.15' 
    }
}

こちらはPlayStoreの認証をする際に必要かと思うので、リリースする場合のみ追加(デバッグ環境で試したいだけであれば不要と推測)

android/app/build.gradle
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    // Added for Google Sign in for Release apps
+   implementation 'com.google.android.gms:play-services-auth:20.4.1'
}

3. Flutterでログイン機能を実装

1. スコープを設定してGoogleSignInクラスを生成

//Default definition
GoogleSignIn googleSignIn = GoogleSignIn(
  scopes: [
    'profile',
    'https://www.googleapis.com/auth/photoslibrary',
  ],
);

//If current device is Web or Android, do not use any parameters except from scopes.
if (kIsWeb || Platform.isAndroid ) {
  googleSignIn = GoogleSignIn(
    scopes: [
      'profile',
      'https://www.googleapis.com/auth/photoslibrary',    ],
  );
}

//If current device IOS or MacOS, We have to declare clientID
//Please, look STEP 2 for how to get Client ID for IOS
if (Platform.isIOS || Platform.isMacOS) {
  googleSignIn = GoogleSignIn(
    clientId:
        "YOUR_CLIENT_ID.apps.googleusercontent.com",
    scopes: [
      'profile',
      'https://www.googleapis.com/auth/photoslibrary',
    ],
  );
}

2. サインイン

signIn()メソッドでサインイン。戻り値としてGoogleSignInAccountクラスを返却。

final GoogleSignInAccount? googleAccount = await _googleSignIn.signIn();
GoogleSignInAccountクラス
class GoogleSignInAccount implements GoogleIdentity {
  ...
  
  final String? displayName;
  
  final String email;
  
  final String id;
  
  final String? photoUrl;
  
  final String? serverAuthCode;
  ...
}

3. サインアウト

signOut()メソッドでサインアウト。戻り値に現在のユーザーのGoogleSignInAccountクラスを返却。

final GoogleSignInAccount? googleAccount = await _googleSignIn.signOut();

オマケ1:サインイン状態の監視

onCurrentUserChangedで認証状態に応じてGoogleSignInAccountのStreamを取得する事が可能。

_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
      // 変更に応じた処理
    });

オマケ2:signInSilentlyメソッド

まだサインインしていない場合のみ認証プロセスを発動し、サインインしている場合は認証プロセスを発動しないsignInメソッド。ユーザーがsignOutもしくはdisconnectの場合のみ認証プロセスに進む。

final GoogleSignInAccount? googleAccount = await _googleSignIn.signInSilently();

オマケ3:disconnectメソッド

サインイン中のユーザーをサインアウトと同時にアプリから切り離すメソッド。google photoへのアクセス許可などを行うとそれ以後は引き続きアクセス権限が保持されるが、disconnect()を使うと権限が完全にリセットされる。

final GoogleSignInAccount? googleAccount = await _googleSignIn.disconnect();

https://developers.google.com/identity/sign-in/android/disconnect

メモ

  • パッケージのドキュメントではFirebaseAuthを使う前提でFirebaseアカウントを作成する様に記載されているが、実際にはFirebaseへの登録で裏で「OAuth 2.0 クライアント」としてアプリが登録されているっぽい。
  • その為、Firebase連携を行わずとも直接GCPに「OAuth 2.0 クライアント」として登録しておけば良さそう。

参考

https://medium.com/codebrew/flutter-google-sign-in-without-firebase-3680713966fb
https://qiita.com/kami_teru/items/8b72cbb2f4adfa10309a

heyhey1028heyhey1028

Google Photo APIを使う前準備

GoogleSignInのOAuth認証を使って、Google Photo APIを実行する為、まずGoogle Photo APIを使う準備。

手順

1. GCPにてPhotos Library APIを有効にする

2. OAuth同意画面を設定

  1. UserType → 外部
  2. アプリ登録の編集
    • アプリ情報(必須)
    • アプリのロゴ
    • アプリのドメイン
    • デベロッパーの連絡先情報(必須)
  3. スコープ → 「スコープを追加または削除」から.../auth/photoslibraryを選択
  4. テストユーザー → テストユーザーのメールアドレスを登録

    公開ステータスが「テスト中」に設定されている間は、テストユーザーのみがアプリにアクセスできます。

heyhey1028heyhey1028

Oauth2.0

OAuth2の4つのアクセス権限付与フロー

  • Authorization Code Grant (認可コード・グラント) :
    • 最も一般的なOauth2.0フロー
    • ユーザーからの権限委譲を必要とするフロー
    • クライアントとリソースオーナーが異なる場合に使われる認可フロー
    • Webやモバイルなどユーザーが存在し、クライアント自身とリソースオーナーが異なる為、権限委譲してもらう必要がある場合に使う
  • Client Credentials Grant (クライアントクレデンシャル・グラント):
    • クライアントが自身のリソースに対してアクセスする場合に使われる認可フロー
    • 自身がリソースオーナーなので、ユーザーなどによる権限委譲が必要ない場合に使う
  • Resource Owner Password Grant (リソースオーナーパスワード・グラント):
    • リソースオーナーのユーザー名とパスワードがクライアントによって直接トークンエンドポイントに送信するフロー
    • 従来のBasic認証と同じようなフロー
    • 利用は推奨されていない
  • implicit Grant (インプリシット・グラント) :
    • 認可コードを挟まず、ユーザーの認可後、アクセストークンがそのままクライアントに返されるフロー
    • アクセストークン漏洩のリスクがある為、利用は推奨されていない

Authorization Code Grantの流れ

Web、モバイルアプリケーションのクライアント側で実装する一般的なOauth2の認可フローである「Authorization Code Grant (認可コード・グラント)」の流れについて整理
1.

参照

https://atmarkit.itmedia.co.jp/ait/articles/1209/10/news105.html