この記事はこの記事の続きです
[LINE Bot] LIFFとリッチメニューでも管理画面が作りたい! 2 -リッチメニュー切替-
ここまでのまとめ
さて、ここまでで「認証用のLINEグループを作る」「グループへの招待・退出」でメニューを切り替える、というところまでできました。
あとはLIFFで作った管理画面に飛ばせばOKですが、当然そのさきのAPIでも認証をかけないといけません。
構成図
ちなみに構成は次のようになっています。
- LIFF : ユーザ用メニューと管理者メニュー (React)
- bot : linebotのロジック(Go)
- line_api : LIFFからLINEの認証などのLINE固有の処理をするAPI (Go)
- omoinas : アプリケーションのメインロジックやmodel・repository層など (Rust)
LIFFでの認証の考え方
今回はログインしているユーザのLINE IDを見て管理者かどうかを判別します。
さて、LIFFでLINE IDを取得するにはsocial api 2.1 を使えば良さそうです。
が、公式マニュアルにもあるように、
LIFFでLINEのUserIDを取得して、それを直接サーバに送り付けてはいけません。
(直接、ユーザIDヲ指定してAPIを叩けてしまうとセキュリティ的によろしくない)
上記のドキュメントにある通り、LIFFではアクセストークンを取得し、サーバー側でプロフアイルを取得してユーザIDを確認します。
LIFFとWebページを作る
LIFFを使うには、LINE Developers から新しく「LINE Login」チャネルを作成します。
なお、現在はLine Messaging APIでLIFFアプリは作れません。
今回はReactで作りました。
Reac/TypescriptでLIFFを作るためのあれこれ
意外と面倒です。。。
今回は create-react-appで作りましたが、はじめGatsbyあたりを使おうとして色々読み込み方がわからず、というのがありました。。
LIFFを読み込む
<html>
<head>
<script src="https://static.line-scdn.net/liff/edge/2.1/sdk.js"></script>
</head>
...
</html>
Typescript用の定義ファイルを追加する。
npm install --save liff-type
環境変数にLIFFのIDを書く
# 開発用
REACT_APP_DEV_LIFF_ID="xxxx"
# 本番環境用
REACT_APP_PRD_LIFF_ID="yyyy"
環境変数の切り替えについて
LIFFの開発では、UIの確認程度ならローカルでもできますが、
結局はサーバー上で確認しないとけないため、開発環境と本番環境の構築と切り替えをしないといけません。
process.env.NODE_ENVは、 npm startではdevelopmentになりますが、
npm run build すると強制的に prodcutionになる為、
今回は REACT_APP_ENVという変数を使い、npm run buildするときに環境変数を指定することにしました。
# 開発環境用の変数でビルドする
REACT_APP_ENV=dev npm run build
LIFF初期化
LIFF初期化の注意点としては、liff.login()をかける前はURLがエンコードされており、そのままではルーティングされません。
そのため、Routerの外側でまずLIFFの初期化を行い、LIFFがURLをデコードするときちんとルーティングされます。
import React from 'react';
import {
BrowserRouter as Router,
Route,
Switch,
} from 'react-router-dom';
... 何やらかんやら初期化
function App() {
liff.init({ liffId: (process.env.REACT_APP_ENV === "prd"
? process.env.REACT_APP_PRD_LIFF_ID
: process.env.REACT_APP_DEV_LIFF_ID)as string }).then(() => {})
return (
<MuiThemeProvider theme={theme}>
<CssBaseline />
<Router>
<Switch>
<Route path="/" exact>
This is liff.
</Route>
<Route path="/:env/user/omise/:clientId/:omiseId" exact>
<ユーザ用のページ />
</Route>
<Route path="/:env/staff/omise/:clientId/:omiseId" exact>
<管理用ペーシ />
</Route>
</Switch>
</Router>
</MuiThemeProvider >
);
}
export default App;
管理者用のページでアクセストークンを取得する
公式ドキュメントに従い、LIFFの初期化とアクセストークンの取得をします。
import React, { useState, useEffect } from 'react'
import { useParams } from 'react-router-dom';
import { useForm, Controller } from "react-hook-form";
// ↓サーバサイドのAPI
import {getOmise, Omise, setOmise, OmiseForm} from 'utils/api/omise';
... 色々コンポーネント読み込みとか
function StaffOmise() {
console.log("aaaa")
const {env, clientId, omiseId, charaId} = useParams<RouteParams>();
...
// ロード時にデータ読み込む処理
const load = () => {
getOmise(env, clientId, omiseId, (omise: Omise) => {
... // フォームへの値の設定とか
})
}
// 初期化処理
useEffect(() => {
liff.ready.then(() => {
let accessToken = ""
if (!liff.isLoggedIn()) {
// ログインしていなければログインさせる。(ローカル環境ではスルーする)
if (process.env.NODE_ENV === "production") {
liff.login({})
}
} else {
accessToken = liff.getAccessToken()
setToken(accessToken)
}
load()
})
},[env, clientId, omiseId, charaId])
return {
...フォームとか
}
}
export default StaffOmise;
API呼び出す時にLINEのトークンを付与する
上記でアクセストークンを取得したので、APIのヘッダーにつけます。
// 更新API
export function setOmise(token: string, ...パラメータ) {
fetch (
env === 'prd'
? `${process.env.REACT_APP_PRD_LINE_API_HOST}/line-api/omise/set`
: `${process.env.REACT_APP_DEV_LINE_API_HOST}/line-api/omise/set`,
{
method: "POST",
mode: "cors",
cache: "no-cache",
headers: { 'Authorization': 'Bearer: '+token},
body: JSON.stringify({
...更新するデータ
}),
}
)... // thenとかcatchとか
}
サーバサイドで認証する
これでLIFFからLINEのアクセストークンが送信されますので、これを元にLINEのユーザIDを取得します。
func setOmise(request events.APIGatewayProxyRequest) (string, error) {
// ちょっと雑ですが、ヘッダーからアクセストークンを取得します。
accessToken := strings.TrimPrefix(request.Headers["Authorization"], "Bearer: ")
if accessToken == "" {
// 401
return "", errors.New("access token is empty.")
}
// ユーザ名取得
prof, err := client.GetUserProfile(accessToken).Do()
if err != nil {
log.Println(err)
return "", err
}
param = {...}
param.UserID = prof.UserID
... // ラムダでRustの更新APIを呼び出し、そこでDynamoDBからユーザIDをチェックする。
payload, _ := json.Marshal(param)
res, err := lambda.New(session.New()).Invoke(&lambda.InvokeInput{
FunctionName: aws.String(ARN + "hoge-" + os.Getenv("ENV") + "-setOmise"),
Payload: payload,
InvocationType: aws.String("RequestResponse"),
})
if err != nil {
log.Println(err)
return "", err
}
}
(Rustで書いたラムダは当然、このAPIからしか呼び出せないように設定しておきます)
まとめ
以上で、LIFFで管理画面を作ってリッチメニューを切り替えたり認証したりする方法を紹介しました。
システムを開発すると、何かにつけて管理画面が必要になりますし、
作ったら作ったで、やれスマホ対応しろだのメアド・パスワード機能をつけろ、というのが世の常です。
ですが、管理画面なんてそもそもシンプルな方が良いですし、パスワード管理なんてしたくありません。
(Auth系サービスもありますが、結局、招待やらアカウント管理やら手間がかかります)
できるだけシンプルにしたい! という発想からの試みでした。