React と CDK で LIFF アプリを作ってみよう
みなさんは LIFF アプリを作ったことはありますか? LIFF アプリとは LIFF(LINE Front-end Framework) を利用した Web アプリケーションのことを指します。 この LIFF と React や Vue などのフレームワークを利用することで、フロントエンド側で簡単に LINE ログインや LINE メッセージ送信などを実装することができます。
LIFF アプリでは以下のようなサービスを実現することができます。
- LINE活用One to Oneマーケティング 開発・運用コストを抑えたデジタル顧客接点作り
- メガネスーパー各店舗で使えるLINEを活用した「アイケアアンバサダー」の開発を支援 〜LINEを活用した友達紹介クーポンで口コミマーケティングを促進〜
最近話題の LINE ミニアプリも LIFF を利用したプラットフォームです。
ということで本ブログでは、React と CDK を利用して LIFF アプリを作成する方法について紹介しようと思います。
進め方
サンプルアプリをデプロイし、そのコードにおける重要な箇所を説明していきます。React(React Native for Web) や CDK についての理解が浅い方は、先に以下を学習してください。
- React Tutorial
- Create React Appで作成したReactアプリにReact Native for Webを導入する
- AWS CDKで Infrastructure as Code インフラの構築・改善をもっと早く!
- CDK WorkShop
本ブログのゴール
この環境を作成していきます。
バックエンドのリソースはサーバーレス構成( 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 アプリのイメージはつかめましたか?次回はもう少し本格的なアプリを作ってみようと思います!!