はじめに
実務で、LINEWORKS/LINE×AWSサーバーレスでPoC開発をしました。
認証基盤を外部サービスに任せるため、そのサービスの設計思想がアーキテクチャに影響を与え、それぞれでアーキテクチャを組む必要がありました。ここでは、そのアーキテクチャをそれぞれ紹介しようと思います。
※本記事の構成・テーブル設計は、実案件の内容をそのまま記載したものではなく、説明用に簡略化・一般化したものです。
前提
ここでは、LINE WORKS/LINEアプリをAWSサーバーレス構成で開発する場合の認証設計について扱います。
主に、フロントエンドで取得したトークンを、バックエンド側でどのように検証し、API Gateway配下のLambdaを保護するかに焦点を当てます。
構成は以下を前提とします。
- BtoB向けアプリの開発
- フロントエンドはWOFF/LIFFアプリ
- バックエンドはAWSサーバーレス構成
- API Gateway
- Lambda Authorizer
- ロジック用Lambda
- DynamoDB
- 認証基盤そのものはLINE WORKS/LINE側に委任する
また、実案件ではWAFなどの防御層も検討しますが、本記事では認証設計とアーキテクチャの考え方を中心に整理します。
LINEWORKSのアーキテクチャ
各コンポーネントの責務
フロント
- WOFF/LIFFアプリ初期化
- ログイン
- アクセストークン/IDトークンを取得してリクエスト
API Gateway
- フロントからリクエストが来ると、Lambda Authorizerを発火
- Lambda AuthorizerからTrueとユーザー情報が返ってくればロジックLambdaを発火
Lambda Authorizer
- アクセストークン検証をリクエスト
- ユーザー情報が返ってくればAPI GatewayにTrueとユーザー情報を返す
ロジックLambda
- ビジネスロジックを実行
設計思想
Cognitoを使わず、外部サービスでの認証
自社でweb開発実績が少ないこと、開発者が自分1人であることから、認証周りのセキュリティ的責任を外部に委任したかった
アクセストークン/IDトークンでの検証
悪意ある第三者による外部からAWS環境へのアクセスを防ぎたかった
Lambda Authorizer
認証ロジックを共通化したかった
LINEWORKS/LINEの違い
LINEのアーキテクチャを紹介する前に、LINEWORKSとLINEの違いを説明します。
LINEWORKS
- 無料版だと1テナント(団体)で30ユーザーまで
- BtoBの設計思想(別テナントからアプリに入れない)
LINE
- 無料版だとpushメッセージを月200通まで
- BtoCの設計思想(LINEのアカウントさえあればアプリを利用可能)
LINEでBtoB向けのアプリ(特定のユーザーのみ使える)を開発する場合には、以下のようにアーキテクチャを組みました。
LINEのアーキテクチャ
DB設計
- LINEユーザーと社員マスタの二つのエンティティをDBに持たせる
- LINEユーザーと社員マスタの両方に、紐づけがされてるかの属性を持たせてる
1. LINEユーザーエンティティ
| 項目 | 値 |
|---|---|
| PK | USER#<lineUserId> |
| SK | #METADATA |
属性一覧
| 属性名 | 型 | 説明 | 例 |
|---|---|---|---|
status |
String | フォロー状態 |
ACTIVE / BLOCKED / UNFOLLOWED
|
linkStatus |
String | 社員 連携状態 |
LINKED / UNLINKED
|
2. 社員エンティティ
| 項目 | 値 |
|---|---|
| PK | EMP#<employeeId> |
| SK | #METADATA |
属性一覧
| 属性名 | 型 | 説明 | 例 |
|---|---|---|---|
linkStatus |
String | LINE 連携状態 |
LINKED / UNLINKED
|
passwordHash |
String(bcryptでハッシュ化) | パスワード | $2b$... |
大まかな流れ
- 公式アカウントを友達登録すると、LINEユーザーを登録(④)
1. LINEユーザーエンティティ
| 項目 | 値 |
|---|---|
| PK | USER#U0001 |
| SK | #METADATA |
status |
ACTIVE |
linkStatus |
UNLINKED |
2. 社員エンティティ
| 項目 | 値 |
|---|---|
| PK | EMP#E001 |
| SK | #METADATA |
linkStatus |
UNLINKED |
passwordHash |
$2b$... |
- アプリを開いて、社員マスタと紐づけがされてなかったら(⑨)、認証画面(IDとパスワード)を表示
- IDとパスワードを入力して送信し、正しければ両エンティティの紐づけの状態を更新する(⑩)
1. LINEユーザーエンティティ
| 項目 | 値 |
|---|---|
| PK | USER#U0001 |
| SK | #METADATA |
status |
ACTIVE |
linkStatus |
LINKED |
2. 社員エンティティ
| 項目 | 値 |
|---|---|
| PK | EMP#E001 |
| SK | #METADATA |
linkStatus |
LINKED |
passwordHash |
$2b$... |
lineUserId |
U0001 |
設計意図
ユーザーエンティティ LINEユーザー+社員マスタ vs LINEユーザーのみ
LINEユーザーは不特定多数の人が登録できるため、社員認証用として別エンティティを用意しました。
社員マスタでの認証 ID+パスワード vs ID+ワンタイムパスワード(Amazon SES)
メールだと、誤送信や再送制御などの運用負荷がかかるので、パスワードを採用しました。
社員エンティティにも紐づけ情報を保持
一度紐づけられてた社員証を使わせないようにしました。例えば、Aさんが社員Xとして紐づけられた後、Bさんが社員Xとして紐づけられてしまうのを防ぎます。
考慮した脆弱性の対策
「安全なWebアプリケーションの作り方」を読んで、以下の脆弱性を洗い出し、対策を立てました。
- アカウントロック
LINEアカウントごとにログイン試行回数を記録し、3回連続で失敗すると30分ロック。また、ログイン失敗率を計測し、それが急速に伸びてるときはプッシュ通知する。 - エラーメッセージ
ID、パスワード、アカウントロックのどれがエラーの原因かメッセージから特定できないようにする。
今後の課題
- リクエストごとにLambda Authorizerを発火させないため、キャッシュ化する

