LIFFで取得したユーザー情報をサーバーサイドで利用するときIDトークンとアクセストークンのどちらを利用するのがいいか比べてみた

# 概要

LIFFのメソッドを使えばLINEのユーザー表示名とかプロフィール画像とかのユーザー情報を取得することができます。
しかし、そのままサーバーに送信することは、なりすましやその他の攻撃に対して脆弱になるため公式ドキュメントにも「しないでください」と明言されています。

その代わりとして、サーバーサイドでユーザー情報を安全に利用する方法として以下の2つが用意されていますが、はたしてどちらを使ったらいいのでしょう？
そこで両者の違いを調べた結果を整理してみました。

* [IDトークンを送信してユーザー情報を取得する](https://developers.line.biz/ja/docs/liff/using-user-profile/#sending-id-token "IDトークン")
* [アクセストークンを送信してユーザー情報を取得する](https://developers.line.biz/ja/docs/liff/using-user-profile/#sending-access-token "アクセストークン")


# はじめに

### LIFFとは

「LINE Front-end Framework」の略で、LINE社が提供するWEBアプリのプラットフォームの一種です。
LINEのトーク上にWEBアプリを表示することができ、ユーザー情報の活用したりユーザーに代わってメッセージを送信したりといったことができます。
v2 以降は外部ブラウザ（IEは除く）でもLIFFアプリが動作するようになりました。

詳しくは[こちら](https://developers.line.biz/ja/docs/liff/overview/)をご覧ください。

### IDトークンとアクセストークン

IDトークンは、ユーザー情報を含むJSONウェブトークン（JWT）です。構成内容はJWTの仕様に準じています。

アクセストークンは、LINEが発行したトークンで、Social API にリクエストを送信するために使用されます。

# ユーザー情報の取得手順

## IDトークンを送信してユーザー情報を取得する

まずは、IDトークンを利用してユーザー情報を取得する手順です。
サーバーサイドはPHPで取得した手順を記載しています。

### IDトークンを取得する

`liff.getIDToken` を呼び出すことで「生のIDトークン」を取得することができます。
ただし、LIFFアプリを登録するときにスコープの「openid」にチェックを入れておかないとnullが返されます。

![LIFF_Scope.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157083/6408d6d1-4dbc-9a82-6055-5e4a367e4328.png)

``` javascript
const idToken = liff.getIDToken();
```

### IDトークンを検証する

IDトークンを検証することによってユーザー情報を取得することができます。

``` php
$ch = curl_init();

$url = 'https://api.line.me/oauth2/v2.1/verify';

$data = [
    'id_token' => $idToken, // LIFFから送信されたIDトークン
    'client_id' => '1234567890', // LIFFアプリを登録したLINEログインチャネルのチャネルID
];

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));

$response = curl_exec($ch);

curl_close($ch);

print_r(json_decode($response, true));
```

``` php:レスポンス
Array
(
    [iss] => https://access.line.me
    [sub] => U1234567890abcdef1234567890abcdef // ユーザーID（友達登録で利用するIDとは別物）
    [aud] => 1234567890 // チャネルID
    [exp] => 1600787661 // IDトークンの有効期限（生成時間から1時間）
    [iat] => 1600784061 // IDトークンの生成時間
    [amr] => Array
        (
            [0] => linesso
        )

    [name] => フリーニア // ユーザーの表示名（スコープでprofileにチェックしている場合のみ）
    [picture] => https://profile.line-scdn.net/aBcdefg123456 // ユーザーの画像のURL（スコープでprofileにチェックしている場合のみ）
    [name] => freeneer@example.com // メールアドレス（取得する認可がある場合のみ）
)
```

## アクセストークンを送信してユーザー情報を取得する

続いて、アクセストークンを利用してユーザー情報を取得する手順です。

### アクセストークンを取得する

`liff.getAccessToken` を呼び出すことでアクセストークンを取得することができます。
アクセストークンの有効期限は発行後12時間です。また、LIFFアプリを閉じると、アクセストークンは無効になります。

``` javascript
const accessToken = liff.getAccessToken();
```

### アクセストークンを検証する

アクセストークンからユーザー情報を取得する前に、アクセストークンが有効かどうかを確認します。
有効時間はもちろんですが、チャンネルIDが正しいかも確認する必要があります。

``` php
$ch = curl_init();

$url = 'https://api.line.me/oauth2/v2.1/verify?access_token=' . $accessToken;

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

$response = curl_exec($ch);

curl_close($ch);

print_r(json_decode($response, true));
```

``` php:レスポンス
Array
(
    [client_id] => 1234567890 // チャネルID
    [expires_in] => 43196 // アクセストークンが有効な残りの秒数
    [scope] => profile openid
)
```


### ユーザーのプロフィールを取得する

アクセストークンが有効だと判明したら、ユーザー情報（プロフィール）を取得します。
Social API にリクエストを送信するときにヘッダにアクセストークンを含めるという本来の使い方ですね。

``` php
$ch = curl_init();

$url = 'https://api.line.me/v2/profile';

$headers = [
    'Authorization: Bearer ' . $token,
];

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

$response = curl_exec($ch);

curl_close($ch);

print_r(json_decode($response, true));
```

``` php:レスポンス
Array
(
    [userId] => U1234567890abcdef1234567890abcdef // ユーザーID（友達登録で利用するIDとは別物）
    [displayName] => フリーニア // ユーザーの表示名
    [pictureUrl] => https://profile.line-scdn.net/aBcdefg123456 // ユーザーの画像のURL
    [statusMessage] => ステータスメッセージ
)
```

## 取得できるユーザー情報

| ユーザー情報 | IDトークン | アクセストークン |
|:-:|:-:|:-:|
| ユーザーID | ○ | ○ |
| 表示名 | ○ | ○ |
| プロフィール画像 | ○ | ○ |
| メールアドレス | ○（※） | × |
| ステータスメッセージ | × | ○ |

※取得する認可がある場合のみ

# 結論

* アクセストークンを利用する場合、APIを2回呼び出す必要があるので時間のロスが発生します。
* 取得できる項目に違いがあるため、用途に応じて使い分ける必要があります。