【コード公開】LIFEアプリ作成手順|LINEログイン機能を実装する方法①

【コード公開】LIFEアプリ作成手順 DX化
【コード公開】LIFEアプリ作成手順

こんにちは、「ReactでのLINEログイン機能の実装方法」について、コードと共に以下の3記事で解説します。

LINE認証を活用することで、ユーザーはわずか数タップで会員登録やログインが完了し、離脱率の大幅な低下につながります。特にモバイルファーストの時代、LINEユーザー8,900万人以上という日本の市場特性を考えると、LINEログインは欠かせない機能です。

本記事では、実装の手順を初心者でも理解できるようにサンプルコードをもとにに解説します。※紹介するコードはサンプルとなるため、実際使用しているフレームワーク、言語に合わせて微調整をお願いします。

ReactでLINEログイン機能を実装する準備と概要

LINE Developer
LINE Developer

今回は、Reactで実装する手順を紹介します。まず全体像です。LINEログインの場合、LINE Developersの設定からフロントエンドの実装まで、いくつかのステップが必要となります。

LINEログインとは?

LINEログインは、OAuth 2.0プロトコルに基づいた認証システムです。ユーザーがLINEアカウントを使って外部サービスにログインできる仕組みで、メールアドレスやパスワードを入力する手間を省略できます。

  • 仕組み: OAuth 2.0認証フローを利用しています。ユーザーがLINEログインボタンをクリックすると、LINEの認証画面に遷移し、許可を与えることでアクセストークンが発行されます。このトークンを使って、アプリケーションはLINE APIを通じてユーザー情報にアクセスできます。
  • 利用シーン: LINEログインは以下のようなシーンで特に効果を発揮します。
    • 会員制Webサイトやアプリの認証システム
    • イベント予約や商品購入時の顧客情報取得
    • LINEの友だち追加と同時にユーザー登録を行うオムニチャネル施策
    • 顧客情報とLINE IDを紐づけたCRM構築

LINEログインに必要なもの

LINEログインを実装するには、以下の準備が必要です。

  • LINE Developersアカウント: まずはLINE Developersコンソールでアカウント登録を行います。企業・個人どちらでも登録可能です。
  • プロバイダー・チャネルの作成: LINE Developersコンソールで「新規プロバイダー作成」を行い、その中に「LINEログイン」チャネルを作成します。ここでチャネルID、チャネルシークレットなどの認証情報が発行されます。
  • LIFFアプリの作成: LINE Front-end Framework (LIFF) アプリを作成し、ReactアプリケーションのURLを「エンドポイントURL」として登録します。LIFFアプリ作成時に発行されるLIFF IDがフロントエンド実装で必要になります。

ReactにおけるLINEログインの流れ

Reactアプリケーションでは、次のような流れでLINEログインを実装します。

  1. LIFF SDKの読み込み: HTML内またはReactコンポーネント内でLIFF SDKをロードします。
  2. LIFF初期化: liff.init()メソッドを使って、LIFF IDとともに初期化します。
  3. ログイン処理: ユーザーがログインボタンをクリックすると、liff.login()が実行され、LINEの認証画面に遷移します。
  4. ユーザー情報取得: ログイン成功後、liff.getProfile()などのメソッドでユーザー情報を取得します。
  5. ページ遷移: ログイン状態に応じて、適切なページコンポーネントを表示します。

これらの基本的な流れを押さえた上で、次に具体的な実装方法を解説します。

ReactでLINEログイン機能を実装する方法【コード付き解説】

ここからは、実際のコードを交えながら、Reactアプリケーションへのログイン機能の実装方法を詳しく解説します。

必要なライブラリのインストールと初期設定

まず、必要なパッケージをインストールします。主にLIFF SDKとReact Routerを使用します。

# npmの場合
npm install @line/liff react-router-dom

# yarnの場合
yarn add @line/liff react-router-dom

次に、HTMLまたはReactコンポーネントでLIFF SDKを読み込みます。2つの方法があります。

方法1: public/index.htmlに直接追加する場合

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>LINE Login with React</title>
    <!-- LIFF SDK -->
    <script charset="utf-8" src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

方法2: Reactコンポーネント内で動的に読み込む場合(特にViteを使用している場合)

import { useEffect } from 'react';
import liff from '@line/liff';

function App() {
  useEffect(() => {
    // LIFF IDは環境変数から読み取るのがベストプラクティス
    const liffId = import.meta.env.VITE_LIFF_ID;
    
    // LIFFの初期化
    liff
      .init({ liffId })
      .then(() => {
        console.log('LIFF初期化成功');
      })
      .catch((err) => {
        console.error('LIFF初期化エラー', err);
      });
  }, []);
  
  return (
    <div className="App">
      {/* アプリケーションの内容 */}
    </div>
  );
}

export default App;

ReactでLINEログイン機能を実装するフォルダ構成

今回作成したLINEログイン機能を実装したReactアプリケーションの標準的なフォルダ構成を紹介します。

基本的なフォルダ構成

line-login-react-app/
├── .env                     # 環境変数(LIFF IDなど)
├── .gitignore               # Gitの除外ファイル設定
├── package.json             # 依存パッケージ管理
├── README.md                # プロジェクト説明
├── public/                  # 静的ファイル
│   ├── index.html           # メインHTMLファイル(LIFF SDK読み込み可)
│   ├── favicon.ico          # サイトアイコン
│   └── manifest.json        # PWA設定
└── src/                     # ソースコード
    ├── index.js             # エントリーポイント
    ├── index.css            # グローバルスタイル
    ├── App.jsx              # メインコンポーネント(ルーティング設定)
    ├── App.css              # Appコンポーネントのスタイル
    ├── components/          # 再利用可能なコンポーネント
    │   ├── Header.jsx       # ヘッダーコンポーネント
    │   ├── Header.css       # ヘッダーのスタイル
    │   ├── Footer.jsx       # フッターコンポーネント
    │   ├── Footer.css       # フッターのスタイル
    │   ├── LoginComponent.jsx # LINEログインコンポーネント
    │   └── MyPage.jsx       # マイページコンポーネント
    │   └── MyPage.css       # マイページのスタイル
    ├── hooks/               # カスタムフック
    │   └── useLiff.js       # LIFF関連のロジックをまとめたフック
    ├── services/            # API通信などのサービス
    │   └── lineAuth.js      # LINE認証関連の処理
    ├── utils/               # ユーティリティ関数
    │   └── auth.js          # 認証関連のヘルパー関数
    └── contexts/            # Reactコンテキスト
        └── AuthContext.jsx  # 認証状態管理用コンテキスト

補足説明

public フォルダ

  • index.html: アプリケーションのベースとなるHTMLファイルです。前述のようにここでLIFF SDKをCDNから読み込むこともできます。
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>LINE Login with React</title>
    <!-- 方法1: CDNからLIFF SDKを読み込む場合 -->
    <script charset="utf-8" src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

src フォルダ

このフォルダには、アプリケーションのソースコードが含まれます。

  • hooks/useLiff.js: LIFF関連のロジックを一元管理するカスタムフックです。
import { useState, useEffect } from 'react';
import liff from '@line/liff';

export function useLiff(liffId) {
  const [isInitialized, setIsInitialized] = useState(false);
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [profile, setProfile] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const initialize = async () => {
      try {
        await liff.init({ liffId });
        setIsInitialized(true);
        setIsLoggedIn(liff.isLoggedIn());
        
        if (liff.isLoggedIn()) {
          const userProfile = await liff.getProfile();
          setProfile(userProfile);
        }
      } catch (e) {
        setError(e.message);
        console.error('LIFF initialization failed', e);
      }
    };

    initialize();
  }, [liffId]);

  const login = () => {
    if (!isInitialized) return;
    liff.login();
  };

  const logout = () => {
    if (!isInitialized) return;
    liff.logout();
    setIsLoggedIn(false);
    setProfile(null);
  };

  return {
    isInitialized,
    isLoggedIn,
    profile,
    error,
    login,
    logout,
    liff
  };
}
  • services/lineAuth.js: LINEの認証処理をバックエンドと連携するロジックを含みます。
// LINE認証情報をバックエンドと連携するための関数群
export const lineAuthService = {
  // バックエンドにLINEのIDトークンを送信して認証
  authenticate: async (idToken) => {
    try {
      const response = await fetch('https://your-api.example.com/auth/line', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ idToken }),
      });
      
      if (!response.ok) {
        throw new Error('Authentication failed');
      }
      
      const data = await response.json();
      return data;
    } catch (error) {
      console.error('LINE Authentication error:', error);
      throw error;
    }
  },
  
  // サーバーから取得したセッショントークンをローカルに保存
  saveSession: (sessionToken) => {
    localStorage.setItem('session_token', sessionToken);
  },
  
  // セッションチェック
  checkSession: async () => {
    const token = localStorage.getItem('session_token');
    if (!token) return false;
    
    try {
      const response = await fetch('https://your-api.example.com/auth/verify', {
        headers: {
          'Authorization': `Bearer ${token}`,
        },
      });
      
      return response.ok;
    } catch (error) {
      return false;
    }
  },
  
  // ログアウト処理
  clearSession: () => {
    localStorage.removeItem('session_token');
  }
};
  • contexts/AuthContext.jsx: アプリケーション全体で認証状態を共有するためのコンテキスト。
import { createContext, useContext, useState, useEffect } from 'react';
import { useLiff } from '../hooks/useLiff';
import { lineAuthService } from '../services/lineAuth';

// 認証コンテキストの作成
const AuthContext = createContext(null);

export function AuthProvider({ children, liffId }) {
  const {
    isInitialized,
    isLoggedIn,
    profile,
    error,
    login,
    logout,
    liff
  } = useLiff(liffId);
  
  const [authState, setAuthState] = useState({
    isAuthenticated: false,
    user: null,
    loading: true
  });
  
  useEffect(() => {
    const checkAuth = async () => {
      if (!isInitialized) return;
      
      if (isLoggedIn && profile) {
        try {
          // バックエンドで認証
          const idToken = liff.getIDToken();
          const authResult = await lineAuthService.authenticate(idToken);
          
          // セッション情報の保存
          lineAuthService.saveSession(authResult.sessionToken);
          
          setAuthState({
            isAuthenticated: true,
            user: {
              ...profile,
              ...authResult.user
            },
            loading: false
          });
        } catch (e) {
          setAuthState({
            isAuthenticated: false,
            user: null,
            loading: false
          });
        }
      } else {
        setAuthState({
          isAuthenticated: false,
          user: null,
          loading: false
        });
      }
    };
    
    checkAuth();
  }, [isInitialized, isLoggedIn, profile, liff]);
  
  const handleLogout = () => {
    logout();
    lineAuthService.clearSession();
    setAuthState({
      isAuthenticated: false,
      user: null,
      loading: false
    });
  };
  
  const value = {
    ...authState,
    login,
    logout: handleLogout,
    error
  };
  
  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

// カスタムフック
export function useAuth() {
  const context = useContext(AuthContext);
  if (context === null) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
}

App.jsx での利用例

認証コンテキストを使った、アプリケーションのルーティング設定例:

import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './contexts/AuthContext';
import Header from './components/Header';
import Footer from './components/Footer';
import LoginComponent from './components/LoginComponent';
import MyPage from './components/MyPage';
import './App.css';

// 認証が必要なルートのラッパー
function ProtectedRoute({ children }) {
  const { isAuthenticated, loading } = useAuth();
  
  if (loading) {
    return <div>Loading...</div>;
  }
  
  return isAuthenticated ? children : <Navigate to="/" />;
}

function AppContent() {
  return (
    <BrowserRouter>
      <div className="app-container">
        <Header />
        
        <main className="main-content">
          <Routes>
            <Route path="/" element={<LoginComponent />} />
            <Route 
              path="/mypage" 
              element={
                <ProtectedRoute>
                  <MyPage />
                </ProtectedRoute>
              } 
            />
          </Routes>
        </main>
        
        <Footer />
      </div>
    </BrowserRouter>
  );
}

function App() {
  // 環境変数からLIFF IDを取得
  const liffId = process.env.REACT_APP_LIFF_ID || import.meta.env.VITE_LIFF_ID;
  
  return (
    <AuthProvider liffId={liffId}>
      <AppContent />
    </AuthProvider>
  );
}

export default App;

フォルダ構成のポイント

  1. パーツの分離: コンポーネント、ロジック、スタイルを適切に分離することで、メンテナンス性が向上します。
  2. 再利用性の向上: カスタムフック(useLiff.js)を作成することで、LINE関連のロジックを複数のコンポーネントで再利用できます。
  3. グローバル状態管理: コンテキスト(AuthContext.jsx)を使うことで、アプリ全体で認証状態を共有できます。
  4. 環境変数の活用: LIFF IDなどの設定値は.envファイルで管理し、環境ごとに切り替えやすくします。
  5. ファイル名の規則: コンポーネントは大文字で始め(Header.jsx)、ユーティリティは小文字で始める(auth.js)など、一貫性のある命名規則を適用します。

このようなフォルダ構成を採用することで、機能拡張やチーム開発がスムーズになります。プロジェクトの規模や要件に応じて、さらに細かく分割したり、状態管理ライブラリ(ReduxやRecoil)を導入したりすることも検討できます。

続いて、実際のログイン処理を実装していきます。→コード公開】LIFEアプリ作成手順|LINEログイン機能を実装する方法②