PHPでLINEログインを実装する方法

本記事は、[めんどい太郎の Advent Calendar 2023](https://qiita.com/advent-calendar/2023/mendoitarou_first_calendar) 10日目の記事です。

# はじめに

この記事は初心者が書いています。

皆さんはLINE使ってますか？

LINEは日本では主要な連絡ツールで多くの人が使っています。

そこで、WebでLINEログインを実装する方法を紹介します。

:::note alert
この記事で紹介するコードは不完全であり、テストのためにセキュリティ面を考慮してない実装があります。
:::

LINEのドキュメントは非常に見やすいので、この記事を見るよりもいいかもしれません。

https://developers.line.biz/ja/docs/line-login/integrate-line-login/

# チャネルの作成

まずは[LINE Developper Console](https://developers.line.biz/console/)にアクセスします。

アクセスすると**プロバイダー**という項目があるので隣りにある**作成**ボタンを押します。

するとプロバイダー名の入力を求められるので入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388444/fcb109c2-cbe4-0979-ed06-dfde070c8716.png)

プロバイダー名には**LINEなどの単語は使えません。**

プロバイダーを作成し、プロバイダーの編集画面に行くと「チャネル設定」という項目があるので開きます。

新規チャネル作成を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388444/d37563ca-ab08-be22-23c7-d22e9000d728.png)

LINEログインを選択します。

国やチャネル名などを入力します。

アプリタイプで**ウェブアプリ**を選択します。

2要素認証の必須化は有効にすることを推奨します。(デフォルトで有効です。)

規約に同意しチャネル作成は完了です。

ここで、「チャネルID」をメモしてください。

また、「チャネルシークレット」を**再発行してから**メモしてください。

これらはあとで使用します。

「LINEログイン設定」の項目に移動し、「コールバックURL」を設定できる人は事前にしておいてください。

今回は``https://example.com/login/callback/``を使用します。(example.comはあくまで例です。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388444/ae886244-e9e9-5fbd-a4f2-ed224104a687.png)

# ページの用意

ログインページを用意します。

以下は例です。

```HTML
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8">
        <title>ログイン</title>
    </head>
    <body>
        <h1>ログイン</h1><br>
        <a href="login_page_url"><img src="../image/btn_login_hover.png"></img></a><br>
    </body>
</html>
```

LINEのログインボタンはドキュメントに従ってください。

https://developers.line.biz/ja/docs/line-login/login-button/

今回はリンク付きの画像を載せています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388444/aabe1e93-f22c-459e-a103-33dcc24ad003.png)

## ログインURLの用意

ログインURLを用意します。

以下は例です。

```PHP
<?php

function rand_str_gene($length) {
    return substr(str_shuffle("ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz0123456789"), 0, $length);
}

$client_id = '';//メモしたチャネルID
$redirect_uri = 'https://example.com/login/callback/';
$redirect_uri = urlencode($redirect_uri);
$state = rand_str_gene(32);
$scope = 'profile%20openid';

$login_url = 'https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=' . $client_id . '&redirect_uri=' . $redirect_uri . '&state=' . $state . '&scope=' . $scope;

file_put_contents("csrf_token.txt", $state);

?>
```

```
https://access.line.me/oauth2/v2.1/authorize?response_type=code
```

までは同じです。ここに``client_id``、``redirect_uri``、``state``、``scope``を指定します。

``client_id``は先程メモしたチャネルIDのことです。

``redirect_uri``は先程指定したものです。

``state``は「クロスサイトリクエストフォージェリ防止用の固有な英数字の文字列」とのことです。

いわゆるCSRFトークンというやつですね。

もちろんページにアクセスされるたびにランダムに生成します。

今回はテストのためにテキストファイルにそのまま保存していますが、こんなことしないでください。

``scope``ではユーザーから取得する情報を指定します。

スコープにメールアドレスを指定する際は申請をする必要があります。

今回はテストなのでプロフィールとIDのみ取得しています。

スコープに関してはドキュメントを見たほうがいいです。

https://developers.line.biz/ja/docs/line-login/integrate-line-login/#scopes

このURLをボタンを押したときに開くようにします。

こんな感じでログイン画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388444/cc5211ec-01ed-0424-432f-86609e01988f.png)

LINEでログインされると``redirect_uri``で指定したページにリダイレクトされます。

## リダイレクト先

リダイレクト先にはGETリクエストが送信されます。

リクエストが送られてきたときにまず確認することはエラーの確認です。

GETリクエストに``error``が含まれていないかをチェックします。

```PHP
if(!empty($_GET['error'])) {
    $error_code = $_GET['error'];
    $error_contents = $_GET['error_description'];
    echo $error_code . ' : ' . $error_contents . '<br>';
}
```

含まれていない場合はトークンの取得を行います。

```PHP
<?php

$client_id = '';//メモしたチャネルID
$redirect_uri = 'https://example.com/login/callback/';
$scope = 'profile%20openid';
$channel_secret = '';//メモしたチャネルシークレット

if(!empty($_GET['error'])) {
    $error_code = $_GET['error'];
    $error_contents = $_GET['error_description'];
} else {
    $get_code = $_GET['code'];
    $get_state = $_GET['state'];

    // CSRFトークンがあっているかどうか確認
    $contents = file("../csrf_token.txt");
    $before_login_gene_token = $contents[0];
    if($before_login_gene_token != $get_state){
      echo 'Error: CSRF_Token';
      exit();
    }

    // トークン入手
    $CURLERR = NULL;

    $data = array(
        'grant_type' => 'authorization_code',
        'code' => $get_code,
        'redirect_uri' => $redirect_uri,
        'client_id' => $client_id,
        'client_secret' => $channel_secret
    );

    $headers = array(
        'Content-Type' => 'application/x-www-form-urlencoded',
    );

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

    $ch = curl_init($url);

    curl_setopt($ch, CURLOPT_POST, TRUE);                            //POSTで送信
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));    //データをセット
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);                    //受け取ったデータを変数に
    $token_response = curl_exec($ch);

    if(curl_errno($ch)){        //curlでエラー発生
        $CURLERR .= 'curl_errno：' . curl_errno($ch) . "\n";
        $CURLERR .= 'curl_error：' . curl_error($ch) . "\n";
        $CURLERR .= '▼curl_getinfo' . "\n";
        foreach(curl_getinfo($ch) as $key => $val){
            $CURLERR .= '■' . $key . '：' . $val . "\n";
        }
        echo nl2br($CURLERR);
    }
    curl_close($ch);
    $json_token_response = json_decode($token_response, true);

    //Error Check
    if(!empty($json_token_response['error'])){
        echo 'Error: <br>';
        echo $json_token_response['error'] . '<br>';
        echo $json_token_response['error_description'] . '<br>';
        exit();
    }
}

?>
```

トークンの取得はPOSTリクエストで行います。

URLは``https://api.line.me/oauth2/v2.1/token``です。

ヘッダーに``Content-Type``を``application/x-www-form-urlencoded``に指定します。

POSTで送信するデータは``grant_type``、``code``、``redirect_uri``、``client_id``、``client_secret``です。

``grant_type``は``authorization_code``で固定です。

``code``はGETリクエストで受け取った値です。

``rediret_uri``、``client_id``はログインURLと同じものを使用します。

``client_secret``はメモしたチャネルシークレットです。

これでPOSTリクエストを送信するとJSONが返ってきます。

このJSONにはトークンなどが記載されています。

軽く紹介しますが詳しくはドキュメントを読むことをおすすめします。

https://developers.line.biz/ja/reference/line-login/#issue-access-token

今回はテストなので``id_token``だけ使用します。

## プロフィール取得

IDトークンを使用してプロフィールを取得します。

```PHP
// 省略
    // レスポンスからIDトークンを変数に保存
    $id_token = $json_token_response['id_token'];

    // GET_Profile(POST)
    $CURLERR = NULL;

    $data = array(
        'id_token' => $id_token,
        'client_id' => $client_id
    );

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

    $ch = curl_init($url);

    curl_setopt($ch, CURLOPT_POST, TRUE);                            //POSTで送信
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));    //データをセット
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);                    //受け取ったデータを変数に
    $profile_response = curl_exec($ch);

    if(curl_errno($ch)){        //curlでエラー発生
        $CURLERR .= 'curl_errno：' . curl_errno($ch) . "\n";
        $CURLERR .= 'curl_error：' . curl_error($ch) . "\n";
        $CURLERR .= '▼curl_getinfo' . "\n";
        foreach(curl_getinfo($ch) as $key => $val){
            $CURLERR .= '■' . $key . '：' . $val . "\n";
        }
        echo nl2br($CURLERR);
    }
    curl_close($ch);
    $json_profile_response = json_decode($profile_response, true);

    //Error Check
    if(!empty($json_profile_response['error'])){
        echo 'Error: <br>';
        echo $json_profile_response['error'] . '<br>';
        echo $json_profile_response['error_description'] . '<br>';
        exit();
    }
}
```

```
https://api.line.me/oauth2/v2.1/verify
```
にPOSTを送ります。

``id_token``に先程入手したIDトークンを``client_id``にメモしたチャネルIDをセットして送信します。

そうすることでプロフィールが取得できます。

以下はドキュメントに記載されていたものを今回実際に返ってきたデータのみに削ったものです。

```JSON
{
  "iss": "https://access.line.me",
  "sub": "U1234567890abcdef1234567890abcdef",
  "aud": "1234567890",
  "exp": 1504169092,
  "iat": 1504263657,
  "amr": ["pwd"],
  "name": "Taro Line",
  "picture": "https://sample_line.me/aBcdefg123456"
}
```

このように返ってきます。

使用しそうな項目のみ紹介しておきます。

| 要素名 | 概要 |
|:-:|:-|
| sub | ユーザーID |
| name | ユーザー表示名 |
| picture | ユーザーアイコンURL |

あとはDBに登録するなり参照するなりするかと思います。

# 終わりに

画像やプログラムに関する説明が少なく、少々わかりにくい記事になってしまったような気がします...

LINEログインはGoogleでログインとは違いボタンが画像なのが扱いづらいかもしれません。

また、LINEログインはトークンを取得してそのトークンからプロフィールを取得して...というように少々手間がかかります。

しかもPOSTです。

Googleでログインはライブラリがあるので一行ぐらいで済みます。ライブラリの偉大さを感じますね...

ですがこれでまた一つSNSログインを増やすことができました！

自身のサービスにSNSログインを実装してユーザーが簡単にユーザー登録できるサイトへ！