【Flutter】Firebase AuthとLINE SDKでLINEログインを実装する
まえがき
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 (ロジック)
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.gradle
のminSdkVersion
を21(以上)にする。(元からなっている場合はそのままでOK)
defaultConfig {
minSdkVersion 21 // 21以上にする
main.dartに追記
iOS/(Android)の設定ができたら、main.dart
にLineSDK.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にログインした。
無事ログインに成功した!!
Firebaseのコンソールを見てみると、userが追加されているのがわかる。
ちなみにこのユーザー UID
は、LINEから取得したuserProfile.userId
になっている。
ログアウト→再度ログインしてみたところ、アカウントが重複することなく無事同じuserとしてログインできた。
後ほどiPhoneでも、同じLINEアカウントを使用してログインを試したところ、こちらも無事成功した。
さいごに
長かったが、Firebase AuthとLINE SDKを組み合わせて、LINEログインを実装することができた。
ただ、本アプリはリリース前のため、本番環境で動作するかはまだ検証していない。
もしかしたら他にも設定が必要かもしれないので、そちらは分かり次第追記予定。
Firebase Authのカスタム認証以外の(Clound Functionsを使わずに済む)方法も、改めて検討したい。
こんな記事もあり、LINEログインの必要性を再検討してみてもいいかも・・・。
追記
Cloud Functionsを使わないやり方も書いてみた。
参考記事
Discussion
はじめまして。
Firebase Auth と LINE ログインの流れや、LINE Developer のダッシュボード上での作業の流れまでもが整理された素敵な記事だと思いました。
が、セキュリティ的に危険な実装がありますのでコメントさせていただきます。
具体的には、下記の部分で、LINE ログインによって Flutter クライアントで得られた LINE の user ID を直接 Cloud Functions で実装したサーバに送信している点が、
LINE ログインのセキュリティチェックリスト:
の「バックエンドサーバーに、IDトークンやアクセストークンを送信して処理する際のチェックリスト」の
の項目に反するためです。
たとえば、LINE の user ID 文字列が他人に漏れると、誰でもその人として Firebase Auth のカスタムトークン認証によるログインができてしまうような実装になっています。
アクセストークンを使用して新規ユーザーを登録する:
の「危険な方法」の図:
が示すように、user ID をクライアントアプリから直接送信し、使用してはいけません。
代わりに同ページの「安全な方法」の図:
が示すように、生のアクセストークンを送信して、Cloud Functions などのサーバ側でそのアクセストークンの有効性を検証した上で直接 LINE Platform から得られる user ID などを使用してカスタムトークン認証をしなければいけません。
ご指摘ありがとうございます!
セキュリティ面は十分な考慮ができていなかったため、詳細ご説明いただき非常に参考になります。