2
4

More than 3 years have passed since last update.

[LINE Bot] LIFFとリッチメニューでも管理画面が作りたい! 3 -認証編-

Posted at

この記事はこの記事の続きです
[LINE Bot] LIFFとリッチメニューでも管理画面が作りたい! 2 -リッチメニュー切替-

ここまでのまとめ

さて、ここまでで「認証用のLINEグループを作る」「グループへの招待・退出」でメニューを切り替える、というところまでできました。
あとはLIFFで作った管理画面に飛ばせばOKですが、当然そのさきのAPIでも認証をかけないといけません。

構成図

ちなみに構成は次のようになっています。

  • LIFF : ユーザ用メニューと管理者メニュー (React)
  • bot : linebotのロジック(Go)
  • line_api : LIFFからLINEの認証などのLINE固有の処理をするAPI (Go)
  • omoinas : アプリケーションのメインロジックやmodel・repository層など (Rust)

image.png

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を読み込む

public/index.html
<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を書く

.env
# 開発用
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をデコードするときちんとルーティングされます。

src/App.tsx
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の初期化とアクセストークンの取得をします。

src/StaffOmise.rs
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のヘッダーにつけます。

src/utils/api.tsx
// 更新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を取得します。

line_api/set_omise.go

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系サービスもありますが、結局、招待やらアカウント管理やら手間がかかります)

できるだけシンプルにしたい! という発想からの試みでした。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4