🦁

【Flutter】Firebase AuthとLINE SDKでLINEログインを実装する

11 min read

まえがき

FlutterのアプリにFirebase AuthとLINE SDKを使って、LINEログインを実装してみた。
Firebase×LINEのユースケースが多くないのか、あまりまとまった情報がなく苦戦したので、参考程度に手順を書き留めておく。

これがベストプラクティスかどうかは自信がないので、ご指摘、アドバイス等あればコメントいただけると嬉しいです。

概略

LINEログインは、Firebase Authが公式対応していないため、Firebaseのカスタム認証を使用する形で実装する。

カスタム認証にはカスタムトークンが必要だが、それはアプリ(Flutter)側では生成できないので、カスタムトークン取得用にCloud Functionsも使用する。

イメージ図

ログイン画面を作成する

諸々のログイン処理を実装する前に、まずは簡単にログイン画面を作成する。
firebase_authの詳細は割愛するが、すでにパッケージをインストールしている前提。
ちなみに、今回は状態管理にflutter_riverpodを使用して、Viewとロジックを分離している。

ファイル構成は以下の通り。

lib/
 ├ main.dart
 └ app/
    ├ sign_in/
    │  └ sign_in_page.dart (View)
    │
    └ auth_manager/
       └ auth_manager.dart (ロジック)

普段はMVVMを採用することが多いですが、今回はなんとなく分けている程度なので、アーキテクチャはあまり参考にしないでください

sign_in_page.dartのコードは以下の通り。
Sign Inボタンがあるだけのシンプルな画面で、ボタンタップでauth_manager.dart(内容は後述)のログイン処理を呼び出す。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../auth_manager/auth_manager.dart';

class SignInPage extends ConsumerWidget {
  const SignInPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () => 
		  ref.read(authManagerProvider).signInWithLine(),
          child: const Text('Sign In with LINE'),
        ),
      ),
    );
  }

auth_manager.dartの内容は以下の通り。
今回のLINEログインとは直接関係ないが、ログイン状態を判定する処理も記述している。

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final authManagerProvider = ChangeNotifierProvider<AuthManager>(
  (ref) {
    return AuthManager();
  },
);

class AuthManager with ChangeNotifier {
  AuthManager() {
    _firebaseAuth.authStateChanges().listen((user) {
      isLoggedIn = user != null;
      notifyListeners();
    });
  }
  final _firebaseAuth = FirebaseAuth.instance;
  bool isLoggedIn = false;
   
  Future<void> signInWithLine() async {
    // 後ほどここにログイン処理を実装していく
  }
}

LINEログインの実装

LINE Developersのチャンネルを作成する

LINEログインを実装するにあたって、LINE Developersのチャンネルを作成しておく必要がある。
LINE Developerの登録はだいぶ前にやったきりで記憶が曖昧なので割愛。

チャンネル作成

LINE Developerの登録ができたら、こちらのページの今すぐはじめようをクリックして作成を進める。

以下のように、必要事項を入力する。
入力した情報は後から編集できるので、迷ったら適当でOK。


ちなみに、チャンネル名チャンネル説明は、以下のようにログイン時に表示される。
チャンネルアイコンもおそらくここでの表示使われる。
(画像はAndroid)

チャンネル作成完了

作成が完了すると、チャンネルの設定内容が表示される。
ここで確認できるチャンネルIDを後ほど使用するので、メモしておく。

LINEログイン設定

ここでLINEログイン設定を開き、iOS>iOS bundle ID, Android>パッケージ名もそれぞれ設定しておく。
(確認方法は下記の通り)

iOSのiOS bundle ID

以下コマンドでXCodeを開き、Runner>General>bundle Identifierで確認

$ open ios/Runner.xcworkspace/

Androidのパッケージ名

android/app/src/main/AndroidManifest.xmlの最初に書いてある

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="your_package_name">

LINE SDK for Flutterを導入する

ここまででLINE Developers上での設定は終わったので、アプリ側に戻る。

アプリからLINEログインするために、LINE SDKを導入する必要がある。
まずはパッケージを追加する。

iOSの設定

パッケージのReadmeを参考に、ios/Runner/Info.plistに以下の内容を追記。

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleTypeRole</key>
    <string>Editor</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <!-- Specify URL scheme to use when returning from LINE to your app. -->
      <string>line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    </array>
  </dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
  <!-- Specify URL scheme to use when launching LINE from your app. -->
  <string>lineauth2</string>
</array>

また、ios/Podfileに以下の内容を追記。(元からある場合は不要)

target 'Runner' do
  use_frameworks!               // 追記
  platform :ios, '10.0'   // 追記

Androidの設定

android/app/build.gradleminSdkVersionを21(以上)にする。(元からなっている場合はそのままでOK)

defaultConfig {    
    minSdkVersion 21  // 21以上にする

main.dartに追記

iOS/(Android)の設定ができたら、main.dartLineSDK.instance.setup("チャンネルID");を追記する。
チャンネルIDは先ほど作成したチャンネルのIDを入れる(String)。

今回の場合は、riverpod, firebaseを使用しているので、以下のような内容になっている。

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_line_sdk/flutter_line_sdk.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'app/auth_manager/auth_manager.dart';
import 'app/home_page/home_page.dart';
import 'app/sign_in/sign_in_page.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(); // firebaseの初期化
  // ↓に先ほど作成したチャンネルのチャンネルIDを入れる(String)
  await LineSDK.instance.setup("チャンネルID"); 
  runApp(
    // riverpod用の記述
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    final _authManager = ref.watch(authManagerProvider);
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'タイトル',
      // ログイン中:ホーム画面、未ログイン:ログイン画面
      home: _authManager.isLoggedIn ? const HomePage() : const SignInPage(),
    );
  }
}

これでひとまず、アプリ側からLINEログインが可能になった。

Firebaseのカスタムトークンを取得する関数を作成

ここまでで、イメージ図①LINEログインして LINEのuser情報取得ができる状態になった。
次は②LINEから取得したuserIdを使用して Cloud FunctionsでFirebase用のカスタムトークンを作成を可能にするため、Cloud Functionsの関数を作成する。

Cloud Functionsの概要、使い方については割愛。

Firebase Admin SDKの準備

カスタムトークンの作成にはAdmin SDKが必要なので、npmでパッケージをインストール。

path/to/your_app/functions/
$ npm install firebase-admin

Admin SDKの利用にはサービスアカウントのキーが必要なので、Firebaseコンソールのプロジェクト設定から、キー(json)をダウンロードし、path/to/your_app/functions/に配置する。

関数作成→デプロイ

Admin SDKの準備ができたら、path/to/your_app/functions/src/index.tsに関数を書いていく。(今回はTypeScriptを使用)

Cloud Functionsのトリガーはいくつか種類があるが、今回はアプリ側から呼び出すためHTTPリクエストにした。

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";

// 先ほど配置したサービスアカウントのキーのパスを指定する
const serviceAccount = require("../xxxxx.json");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

exports.fetchCustomToken = functions
.region('asia-northeast1')
.https
.onRequest(async(request, response) => {
  const userId = request.body.data.userId;

  // userIdが不正の場合はエラーで終了
  if (typeof userId !== "string"){
    console.log("userId is not string");
    response.status(404).send({
      data: {"error": "userId is not string"},
    });
    return;
  }
  
  const customToken = await admin.auth().createCustomToken(userId);
  response.status(200).send({
    data: {"customToken": customToken},
  });
});

これをデプロイすればOK。

アプリ側のログイン処理を実装

いよいよ最後の手順。

cloud_functionsパッケージのインストール

アプリ側からCloud Functionsを呼び出すため、パッケージをインストールしておく。
パッケージはこちら

auth_manager.dartの最終形

一連の準備が整ったので、いよいよauth_manager.dartを完成させる。
コードは以下の通り。

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_line_sdk/flutter_line_sdk.dart';  // LINE SDK追加
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:cloud_functions/cloud_functions.dart'; // cloud_functions追加

final authManagerProvider = ChangeNotifierProvider<AuthManager>(
  (ref) {
    return AuthManager();
  },
);

class AuthManager with ChangeNotifier {
  AuthManager() {
    _firebaseAuth.authStateChanges().listen((user) {
      isLoggedIn = user != null;
      notifyListeners();
    });
  }
  final _firebaseAuth = FirebaseAuth.instance;
  final _lineSdk = LineSDK.instance;  // LINE SDKのインスタンス
  bool isLoggedIn = false;
   
  // ログイン処理
  Future<void> signInWithLine() async {
    // LINEログインし、LINEのuserIdを取得する
    final result = await _lineSdk.login();
    final lineUserId = result.userProfile?.userId;
    
    // LINEのuserIdを使って、Cloud Functionsからカスタムトークンを取得する
    final callable = FirebaseFunctions.instanceFor(region: 'asia-northeast1')
        .httpsCallable('fetchCustomToken');
    final response = await callable.call({
      'userId': lineUserId,
    });
    
    // functionsから取得したカスタムトークンを使用して、Firebaseログイン
    await _firebaseAuth.signInWithCustomToken(response.data['customToken']);
  }
  
  // ログアウト処理
  Future<void> signOut() async {
      // LINE, Firebase両方でログアウトする
    await _lineSdk.logout();
    await _firebaseAuth.signOut();
  }
}

エラーハンドリングは割愛しているのでご了承ください

冒頭でも説明した通り、まずはLINEログインして、その後カスタムトークン取得→Firebaseログインという流れ。

またログアウトについても、LINE, Firebase両方行う。

いざログイン!!

一通り実装が終わったので、まずはAndroidでビルドしてログインを試してみる。

私の場合は普段iPhoneを使っているので、動作確認のためだけに一度AndroidでLINEにログインした。

PCやiPadであればスマホと並行で利用できるが、複数のスマホでのアカウントの同時利用はできず、Androidでログインしたタイミングで、iPhoneのLINEは情報が削除されて使用不可になるので注意が必要です。

トークのバックアップをとっておくことで、再度iPhoneでログインした際に元の状態に戻すことができるので、事前のバックアップをオススメします。
Androidでの使用時間が長いと、その間のやりとりは復元されず、不完全な復元なってしまうので注意してください。

無事ログインに成功した!!

Firebaseのコンソールを見てみると、userが追加されているのがわかる。
ちなみにこのユーザー UIDは、LINEから取得したuserProfile.userIdになっている。

ログアウト→再度ログインしてみたところ、アカウントが重複することなく無事同じuserとしてログインできた。

後ほどiPhoneでも、同じLINEアカウントを使用してログインを試したところ、こちらも無事成功した。

さいごに

長かったが、Firebase AuthとLINE SDKを組み合わせて、LINEログインを実装することができた。
ただ、本アプリはリリース前のため、本番環境で動作するかはまだ検証していない。

もしかしたら他にも設定が必要かもしれないので、そちらは分かり次第追記予定。

Firebase Authのカスタム認証以外の(Clound Functionsを使わずに済む)方法も、改めて検討したい。

こんな記事もあり、LINEログインの必要性を再検討してみてもいいかも・・・。

https://qiita.com/pochi-sato/items/c1d51f310f5427253a3b

参考記事

https://techblog.yahoo.co.jp/advent-calendar-2018/firebase-flutter-yid/
https://firebase.google.com/docs/auth/admin/create-custom-tokens?hl=ja
https://firebase.flutter.dev/docs/functions/usage
https://csiandal.medium.com/firebase-cloud-function-error-response-is-not-valid-json-object-401221c3cb89

Discussion

ログインするとコメントできます