# IDトークンからプロフィール情報を取得する

LINEプラットフォームは、OpenID Connect (opens new window)仕様に準拠するIDトークンを発行しているため、LINEプラットフォームからユーザーのプロフィール情報(ユーザーID・表示名・プロフィール画像・メールアドレス)を安全に取得できます。

所定の申請等を行った法人ユーザーは、LINE Profile+に登録された情報(氏名・性別・誕生日・電話番号・住所)も取得できます。詳しくは、「LINE Profile+に登録されている情報を取得する」を参照してください。

# IDトークンを取得する

アクセストークンを取得するときに、IDトークンも取得できます。

LIFFアプリでIDトークンを取得することもできます

liff.getIDToken()を利用してIDトークンを取得することもできます。

# IDトークンからプロフィール情報を取得する

IDトークンに含まれる情報を使用する前に、以下のいずれかの方法でIDトークンを検証してください。

# LINEログインAPIのエンドポイントを利用する

アクセストークンと一緒に取得したIDトークンと、LINEログインのチャネルIDをエンドポイントに送信するだけで、IDトークンを検証し、ユーザーのプロフィール情報とメールアドレスを取得できます。

リクエストの例:

curl -v -X POST 'https://api.line.me/oauth2/v2.1/verify' \
 -d 'id_token=eyJraWQiOiIxNmUwNGQ0ZTU2NzgzYTc5MmRjYjQ2ODRkOD...&client_id=1234567890'

レスポンスの例:

{
    "iss": "https://access.line.me",
    "sub": "U1234567890abcdef1234567890abcdef",
    "aud": "1234567890",
    "exp": 1504169092,
    "iat": 1504263657,
    "nonce": "0987654asdf",
    "amr": [
        "pwd",
        "linesso",
        "lineqr"
    ],
    "name": "Taro Line",
    "picture": "https://sample_line.me/aBcdefg123456",
    "email": "taro.line@example.com"
}

詳しくは、『LINEログイン v2.1 APIリファレンス』の「IDトークンを検証する」を参照してください。

# IDトークンを検証するコードを書く

任意のJWTライブラリ (opens new window)を使ったり、独自のコードを書いたりして、IDトークンを検証し、ユーザーのプロフィール情報とメールアドレスを取得できます。

# IDトークン

IDトークンは、ユーザー情報を含むJSONウェブトークン(JWT)です。IDトークンは、ピリオド(.)で区切られたヘッダー、ペイロード、および署名から構成されます。各部分はbase64urlでエンコードされています。詳しくは、JWTの仕様 (opens new window)を参照してください。

ヘッダーには以下の値が含まれます。

プロパティ タイプ 説明
alg String IDトークンの署名アルゴリズム。ネイティブアプリやLINE SDK、LIFFアプリに対してはES256(ECDSA using P-256 and SHA-256)が、ウェブログインに対してはHS256(HMAC using SHA-256)が返されます。
type String ペイロードの形式。JWTが返されます。
kid String 公開鍵ID。algの値が ES256の場合のみヘッダーに含まれます。kidプロパティについて詳しくは、『JSON Web Key (JWK)のドキュメント (opens new window)』を参照してください。公開鍵の取得方法は、「kidを使って公開鍵を取得する」を参照してください。

以下はデコードしたヘッダー部分の例です。

algHS256の場合:

{
  "typ": "JWT",
  "alg": "HS256"
}

algES256の場合:

{
  "typ": "JWT",
  "alg": "ES256",
  "kid": "a2a459aec5b65fa..."
}
# ペイロード

ユーザー情報はペイロード部分に含まれます。

プロパティ タイプ 説明
iss String https://access.line.me。IDトークンの生成URLです。
sub String IDトークンの対象ユーザーID
aud String チャネルID
exp Number IDトークンの有効期限(UNIXタイム)
iat Number IDトークンの生成時間(UNIXタイム)
auth_time Number ユーザー認証時間(UNIXタイム)。認可リクエストにmax_ageの値を指定しなかった場合は含まれません。
nonce String 認可URLに指定したnonceの値。認可リクエストにnonceの値を指定しなかった場合は含まれません。
amr Stringの配列 ユーザーが使用した認証方法のリスト。以下のいずれかの値が含まれます。それぞれの認証方法については「ユーザーがユーザー認証を行う」を参照してください。
  • pwd:メールアドレスとパスワードによるログイン
  • lineautologin:LINEによる自動ログイン(LINE SDKを使用した場合も含む)
  • lineqr:QRコードによるログイン
  • linesso:シングルサインオンによるログイン
name String ユーザーの表示名。認可リクエストにprofileスコープを指定しなかった場合は含まれません。
picture String ユーザープロフィールの画像URL。認可リクエストにprofileスコープを指定しなかった場合は含まれません。
email String ユーザーのメールアドレス。認可リクエストにemailスコープを指定しなかった場合は含まれません。

以下はデコードしたペイロード部分の例です。

{
    "iss": "https://access.line.me",
    "sub": "U1234567890abcdef1234567890abcdef ",
    "aud": "1234567890",
    "exp": 1504169092,
    "iat": 1504263657,
    "nonce": "0987654asdf",
    "amr": ["pwd"],
    "name": "Taro Line",
    "picture": "https://sample_line.me/aBcdefg123456"
}
# 署名

署名を使ってレスポンスの有効性を検証します。Base64urlでエンコードされているヘッダー、ピリオド、およびペイロードを繋げた文字列を値とし、チャネルシークレットを鍵として、HMAC SHA-256アルゴリズムでハッシュ化した値をbase64urlでエンコードしたのが署名です。アプリのセキュリティを保つため、IDトークンの署名は必ず検証する必要があります。

# kidを使って公開鍵を取得する

ネイティブアプリやLINE SDK、LIFFアプリのデコードしたIDトークンのヘッダーに含まれるkidプロパティを使って公開鍵を取得するには、以下の手順に従います。

  1. デコードしたIDトークンのヘッダーkidプロパティを確認します。
  2. LINE OpenID Connect DiscoveryドキュメントURL (opens new window)』にアクセスし、レスポンスオブジェクトを受け取ります。 レスポンスオブジェクトに含まれるプロパティとその値について詳しくは、『OpenID Connect Discovery 1.0 (opens new window)』を参照してください。
  3. レスポンスオブジェクトに、jwks_uriプロパティとその値としてhttps://api.line.me/oauth2/v2.1/certsが含まれています。『JSON Web Key(JWK)ドキュメントURL (opens new window)』にアクセスし、JSON Web Key (JWK) のJSON配列を受け取ります。JWKの仕様について詳しくは、『JSON Web Key (JWK) (opens new window)』を参照してください。
  4. JSON配列の中で、手順1で確認したkidプロパティを含む要素が公開鍵です。

# IDトークンをデコードして検証する

IDトークンをデコードして検証するには、任意のJWTライブラリを使うか、 以下の手順に従います

# JWTライブラリを使う

一般利用が可能なJWTライブラリ (opens new window)を使って、IDトークンをデコードして検証できます。以下はPython®向けライブラリを使ってIDトークンをデコードする例です。

import jwt

decoded_id_token = jwt.decode(id_token,
                              channel_secret,
                              audience=channel_id,
                              issuer='https://access.line.me',
                              algorithms=['HS256'])

# check nonce (Optional. But strongly recommended)
nonce = '_stored_in_session_'
expected_nonce = decoded_id_token.get('nonce')
if nonce != decoded_id_token.get('nonce'):
    raise RuntimeError('invalid nonce')
# IDトークンをデコードして検証する
  1. ピリオド(.)を区切り文字として、ヘッダー、ペイロード、および署名を分割します。
  2. 各部分をbase64urlでデコードします。
  3. Base64urlでエンコードされたヘッダー、ピリオド、およびペイロードを繋げた文字列を値とし、チャネルシークレットを鍵として、ハッシュ値を算出します。この値がデコードした署名と同じであるかどうかを検証します。
  4. issの値がhttps://access.line.meであり、IDトークンがLINEから送信されたものであることを確認します。
  5. audの値がお使いのチャネルIDと一致しており、IDトークンがお使いのチャネルを対象としていることを確認します。
  6. IDトークンの有効性を確認するには、expの値が検証時より後のUNIXタイムスタンプであることを確認します。
  7. リプレイアタックを防止するため、nonceの値が認可リクエストに指定したnonceの値と等しいことを確認します。nonceの値をユーザーセッションと共に保存します。このステップは省略できますが、nonceの値を含めることを強くお勧めします。

以下はPython 3を使った例です。

import base64
import hashlib
import hmac
import json
import time


def base64url_decode(target):
    rem = len(target) % 4
    if rem > 0:
        target += '=' * (4 - rem)

    return base64.urlsafe_b64decode(target)


def check_signature(key, target, signature):
    calc_signature = hmac.new(
        key.encode('utf-8'),
        target.encode('utf-8'),
        hashlib.sha256
    ).digest()

    return hmac.compare_digest(signature, calc_signature)


def decode_id_token(id_token, channel_id, channel_secret, nonce=None):
    # step 1
    header, payload, signature = id_token.split('.')

    # step 2
    header_decoded = base64url_decode(header)
    payload_decoded = base64url_decode(payload)
    signature_decoded = base64url_decode(signature)

    # step 3
    valid_signature = check_signature(channel_secret,
                                      header + '.' + payload,
                                      signature_decoded)
    if not valid_signature:
        raise RuntimeError('invalid signature')

    payload_json = json.loads(payload_decoded.decode('utf-8'))

    # step 4
    if payload_json.get('iss') != 'https://access.line.me':
        raise RuntimeError('invalid iss')

    # step 5
    if payload_json.get('aud') != channel_id:
        raise RuntimeError('invalid aud')

    # step 6
    if int(time.time()) > payload_json.get('exp'):
        raise RuntimeError('invalid exp')

    # step 7 (Optional. But strongly recommended)
    if nonce is not None:
        if payload_json.get('nonce') != nonce:
            raise RuntimeError('invalid nonce')

    return payload_json