React と CDK で LIFF アプリを作ってみよう

2021.01.31

みなさんは LIFF アプリを作ったことはありますか? LIFF アプリとは LIFF(LINE Front-end Framework) を利用した Web アプリケーションのことを指します。 この LIFF と React や Vue などのフレームワークを利用することで、フロントエンド側で簡単に LINE ログインや LINE メッセージ送信などを実装することができます。

LIFF アプリでは以下のようなサービスを実現することができます。

最近話題の LINE ミニアプリも LIFF を利用したプラットフォームです。

ということで本ブログでは、React と CDK を利用して LIFF アプリを作成する方法について紹介しようと思います。

進め方

サンプルアプリをデプロイし、そのコードにおける重要な箇所を説明していきます。React(React Native for Web) や CDK についての理解が浅い方は、先に以下を学習してください。

本ブログのゴール

この環境を作成していきます。

バックエンドのリソースはサーバーレス構成( API Gateway , Lambda )とし、フロントアプリは一旦ローカルで起動します。

作業は以下の順で進めていきます。

  • LINE ログイン/LIFF アプリの登録
  • CDK で AWS リソースをデプロイ
  • ローカル環境のアプリから API を実行

それでは早速やっていきましょう!!

LINE ログイン/LIFF アプリの登録

LIFF アプリで LINE ログインを機能を実装するためには、LINEログインチャネル、およびLIFFアプリの登録が必要です。これらの作業はLINE Developerにて実施します。

LINE Developer にログインし、新規チャネルの作成からLINEログインを作成します。

任意のチャネル名、チャネル説明を入力、アプリタイプとしてウェブアプリを選択し、作成ボタンをクリックします。

作成した LINE ログインに LIFF を追加します。

任意の LIFF アプリ名、サイズを入力、エンドポイント URL にhttps://localhost:3000、Scope を openid とし、作成ボタンをクリックします。

エンドポイント URL は、 Token が発行された後にリダイレクトされる URL です。今回はローカル環境でフロントアプリを起動するのでhttps://localhost:3000とします。また Scope を openid とすることで、 id_token が発行されます。

これにて LINE ログイン/LIFF アプリの登録作業は完了です。チャネル ID、LIFF ID、LIFF URL は後ほど利用しますのでメモしておいてください。

CDK で AWS リソースをデプロイ

次に CDK を利用して AWS リソースを作成します。環境変数のCLIENT_IDに先ほどのチャネル IDをセットし、サンプルアプリを clone したリポジトリにて 以下のコマンドを実行します。※ AWS CLI で AWS に接続できる環境で実施してください。

yarn
yarn backend:build
yarn backend:deploy

このコマンドで以下の AWS リソースを作成しています。

  • API Gateway
  • Lambda Layer
  • Lambda Authorizer
  • Lambda

LINE ログインにおいて重要な役割をはたす Lambda Authorizer について解説します。 Lambda Authorizer は Lambda を使用して API メソッドへのアクセスを制御する機能です。今回は Lambda Authorizer の中で LINE ログインにより発行されたid_tokenを検証し、正常なトークンであればメインの Lambda を実行するような実装としています。

export async function handler(event: APIGatewayTokenAuthorizerEvent): Promise<APIGatewayAuthorizerResult> {
  console.log(event)
  const idToken = event.authorizationToken.split(' ')[1]
  return await fetch('https://api.line.me/oauth2/v2.1/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: `id_token=${idToken}&client_id=${process.env.CLIENT_ID!}`,
  })
    .then(res => res.json())
    .then(json => {
      console.log('Social API Response: ' + JSON.stringify(json, null, 2))
      if (json.error) {
        return generatePolicy('user', 'Deny', event.methodArn)
      } else {
        return generatePolicy('user', 'Allow', event.methodArn, json.sub)
      }
    })
}

また、 Lambda Authorizer の中では後続の Lambda にデータを引き渡すことができるため、id_tokenの sub クレームをcontextにセットしています。

const generatePolicy = (
  principalId: string,
  effect: string,
  resource: string,
  lineUserId?: string,
): APIGatewayAuthorizerResult => {
  const policy = {
    principalId,
    policyDocument: {
      Version: '2012-10-17',
      Statement: [
        {
          Action: 'execute-api:Invoke',
          Effect: effect,
          Resource: resource,
        },
      ],
    },
  }
  return lineUserId ? Object.assign(policy, { context: { lineUserId } }) : policy
}

Lambda Authorizer は以下のブログを参考に実装しています。

データの引き渡しについてもう少し詳しく知りたい方はこちらのブログを参照してください。

ローカル環境のアプリから API を実行

次に ローカル環境でアプリケーションを起動します。環境変数のREACT_APP_LIFF_IDに先ほどのLIFF IDをREACT_APP_API_URLに APIGateway のエンドポイントをセットし以下のコマンドを実行します。

yarn app:start

ローカルのエンドポイントにアクセスしてみましょう。今回のようにブラウザからアクセスする場合はログイン画面が表示されます。※スマホのLINEアプリから起動する場合はこの画面は表示されません。

ログイン後、初回アクセス時にはサービス提供者に対するアクセスを許可画面が表示されます。

その後メインのアプリが起動します。

上記のようにlineUserIdが表示できていれば成功です。

LINE ログインの実装部分について解説します。LINE ログインは OAuth 2.0 と OpenID Connect に準拠するサービスです。そのためこれらのプロトコルに基づき実装することで、id_tokenを取得することができます。 LIFF アプリでは、liff.init()liff.login()を実行することで、自前で複雑な処理を実装することなくid_tokenを簡単に取得することができます。

  React.useEffect(() => {
    const fn = async () => {
      await liff.init({ liffId })
      if (!liff.isLoggedIn()) {
        liff.login()
      }
      const idToken = liff.getIDToken()
      setIdToken(idToken)
      setSignInFinished(true)
    }
    fn()
  }, [])

取得したid_tokenは、 API リクエストの Authorization ヘッダーに付与します。先述の通り、 Lambda Authorizer でこのid_tokenを検証します。

  React.useEffect(() => {
    const fn = async () => {
      setIsCommunicating(true)
      const res = await demoAPI
        .get('/demo', {
          headers: {
            Authorization: `Bearer ${idToken}`,
          },
        })
        .catch(error => console.dir(error))
      if (res) {
        console.log(res)
        setLineUserId(res.data.lineUserId)
      }
      setIsCommunicating(false)
    }
    fn()
  }, [idToken, setIsCommunicating])

一連の流れ

LIFF アプリの一連の流れを改めて説明すると、

  • LIFF アプリは、liff.login()を実行することで、id_tokenを取得する
  • LIFF アプリは、 Authorization ヘッダーにid_tokenを付与しAPIエンドポイントに対しリクエスト投げる
  • バックエンドは、Lambda Authorizer にてid_tokenを検証し、有効な Token であれば後続の Lambda を実行する

のような挙動になります。

まとめ

LIFF アプリのイメージはつかめましたか?次回はもう少し本格的なアプリを作ってみようと思います!!