サーバーにはプロフィール情報ではなくトークンを送ろう
LINEログインを組み込んだりLINEミニアプリを開発したりする過程で、エンドユーザーのユーザーIDや表示名などのプロフィール情報を取得できることにお気づきの方も多いでしょう。取得したプロフィール情報を自社サービスのサーバーへ送れば、そのままログインや会員連携に活用できそうに見えるかもしれません。
しかし、セキュリティの観点から、クライアントはユーザーIDなどのプロフィール情報を、ユーザー認証の根拠としてそのままサーバー側に送信すべきではありません。この記事では、サーバーでユーザーを安全に識別するための基本的な考え方を紹介します。
クライアントから送信されたプロフィール情報は改ざんされうる
たとえば、LIFFアプリでliff.getProfile()を使うと、ユーザーIDや表示名などを取得できます。これらの情報は、LIFFアプリの画面にプロフィールを表示する用途には利用できます。
一方、次のように取得した情報をサーバーへ送り、userIdをログイン中のユーザーとして扱う実装には、セキュリティ上の問題があります。
ブラウザやアプリ上で動くコードと、そのコードから送信されるリクエストは、ユーザーが変更できてしまいます。悪意のある攻撃者があなたの知らないうちにリクエストのuserIdを別の値に書き換えても、サーバーには、それがLINEプラットフォームから取得した値かどうかを判断する材料がありません。この値だけでログインや会員連携を行うと、別のユーザーになりすまされるおそれがあります。
次のシーケンス図は、攻撃者が別のユーザーのユーザーIDを知っている場合に、userIdを書き換えてなりすます流れを示しています。
攻撃者が何らかの方法で別のユーザーのユーザーIDを知り、ログインリクエストのuserIdをその値に書き換えたとします。サーバーが値を検証せずにセッションを発行すると、攻撃者がそのユーザーとしてログインし、会員情報や予約履歴を閲覧したり、ポイントやクーポンを不正に利用したりする可能性があります。ユーザーIDはパスワードではなく、それ自体で本人確認を行える情報ではありません。
同じ理由で、liff.getDecodedIDToken()で取得したプロフィール情報も、サーバーへ送ってユーザー認証に使わないでください。
生のトークンを送り、サーバーで検証する
サーバーでユーザーを識別するときは、クライアントからユーザーIDではなく、生のIDトークンまたはアクセストークンを送ります。そして、サーバーからLINEプラットフォームのAPIを呼び出してトークンを検証し、その結果からユーザーIDを取得します。
IDトークンを使う場合の全体の流れを、次のシーケンス図に示します。
重要なのは、LIFFアプリがプロフィール情報ではなく生のIDトークンを自社サービスのサーバーへ送り、サーバーがLINEプラットフォームから直接受け取った検証結果を使うことです。具体的な手順は次のとおりです。
liff.init()の完了後に、LIFFアプリでliff.getIDToken()を呼び出し、生のIDトークンを取得する- IDトークンをHTTPSで自社サービスのサーバーへ送る
- サーバーから「IDトークンを検証する」エンドポイントへ、IDトークンと期待するチャネルIDを送る
- 検証に成功したレスポンスの
subを、LINEのユーザーIDとして扱う
LINEログインをウェブアプリに組み込む場合は、ログイン後に「コールバックURLへ返された認可コード」を通常はバックエンドからLINEプラットフォームのトークンエンドポイントへ送ります。すると、バックエンドはアクセストークンと、openidスコープを指定している場合はIDトークンを取得できます。この場合は、ブラウザから送られたユーザーIDではなく、バックエンドが取得し検証したトークンの情報からユーザーを識別してください。
アクセストークンを使う方法もあります。サーバーで「アクセストークンの有効性を検証する」エンドポイントを呼び出し、検証結果のclient_idが期待するチャネルIDと一致すること、expires_inが正の値であることを確認します。その後、「ユーザープロフィールを取得する」エンドポイントからユーザーIDを取得します。
検証後は自社サービスのセッションへ交換する
検証済みのユーザーIDを取得したら、自社サービス用のセッションを発行します。以降のリクエストでは、そのセッションを使ってログイン状態を管理します。
- LIFFアプリでは、IDトークンまたはアクセストークンを自社サービスのサーバーへ送る。ウェブアプリに組み込んだLINEログインでは、バックエンドが認可コードをトークンと交換する
- 自社サービスのサーバーからLINEプラットフォームのAPIを呼び出し、トークンを検証する
- 検証結果からユーザーIDを取得する
- 取得したユーザーIDに対して、自社サービス用のセッションを発行する
LINEプラットフォームのトークンを自社サービスのセッションとして長期間使い続ける設計は避けてください。特に、LIFFアプリで取得したアクセストークンは、ユーザーがLIFFアプリを閉じると無効化される場合があります。そのため、トークンをlocalStorageなどに保存して次回の起動時に再利用するような実装は、予期せぬタイミングで認証に失敗する原因になります。
また、IDトークンやアクセストークンは認証情報です。ログや分析ツールに記録せず、HTTPSで送信してください。自社サービスのセッションをCookieで管理する場合は、Secure属性とHttpOnly属性を設定するなど、セッション自体も適切に保護する必要があります。
使い分けの整理
クライアントとサーバーでは、同じユーザー情報でも扱い方が異なります。
| 目的 | 使用する情報 |
|---|---|
| LIFFアプリの画面に表示名やプロフィール画像を表示する | liff.getProfile()またはliff.getDecodedIDToken()で取得した情報 |
| サーバーでユーザーを識別する | liff.getIDToken()またはliff.getAccessToken()で取得した生のトークンをサーバーで検証した結果 |
| 検証後のログイン状態を維持する | 自社サービスが発行したセッション |
まとめ
クライアント側で取得したプロフィール情報は、エンドユーザーに表示する画面には利用できますが、改ざんされる可能性があります。そのため、そのままサーバーでユーザー認証の根拠にするのは危険です。サーバーでユーザーを識別するときは、IDトークンまたはアクセストークンを受け取り、LINEプラットフォームで検証した結果を使用してください。
詳しくは、『LIFFドキュメント』の「LIFFアプリおよびサーバーでユーザー情報を使用する」および『LINEログインドキュメント』の「LINEログインのセキュリティチェックリスト」を参照してください。