[Laravel] LINEログインv2.1を実装する

#今回の題
LINEログインをLaravelにて実装しました。
アウトプットとして残します。

#使用したバージョン
- Laravel 6.8
※Laravelのインストール手順は省略します。
- LINEログインv2.1
- guzzle7.0

#チャネルIDとチャネルシークレットの取得
４ステップに分けて説明していきます。

## 1. 開発者用のアカウントを作成
以下にアクセスし、LINEのアカウントを使って開発者用のページにログインしてください。
[LINE Developers](https://developers.line.biz/ja/)

## 2. プロバイダーの作成

以下のページの左サイドバーのプロバイダーを選択し、画面中央辺りの「作成」を押してください。
[LINE Developers コンソール](https://developers.line.biz/console/)

![スクリーンショット 2020-07-08 5.00.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/702abf98-e064-20fb-9572-37c00793dec3.png)



作成するプロバイダー名を求められるので適当に入力し、「作成」を押したらプロバイダーの作成は完了です。
![スクリーンショット 2020-07-08 5.12.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/846d4acd-fcd4-7e60-a28a-6ef4f08bbbb9.png)


## 3. チャネルの作成
プロバイダーの作成後、以下のようなページに飛ばされます。
画像内の赤枠の**「LINE ログイン」**を選択してください。
![スクリーンショット 2020-09-15 21.58.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/035cfbd9-f9c9-7ffc-f496-169936bbe523.png)


チャネル作成画面に飛びます。
チャネルの名前や説明などは適宜全て入力し、**アプリタイプは「ウェブアプリ」**を選択しておいてください。
「作成」を押したらチャネルの作成は完了です。
![スクリーンショット 2020-07-08 5.20.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/fd4ca0cf-6da3-eadb-c774-98599b91d6cd.png)


## 4. チャネルIDとチャネルシークレットの取得
チャネル作成後、作成したチャネルの設定ページに飛ばされます。
（画像は黒く塗りつぶしています）

- **チャネルID**
チャネル基本設定のタブの一番上にのっています。
あとで使うのでコピーしておいてください。
![スクリーンショット 2020-09-15 22.00.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/3922de97-f7ca-9d09-045a-a3f4192b6099.png)


- **チャネルシークレット**
チャネル基本設定のタブの下の方にのっています。
![スクリーンショット 2020-09-15 22.00.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/e99823cb-a66a-46a4-e0f4-db5b3ecceb8e.png)

以上でチャネルIDとチャネルシークレットがGETできました。

#コールバックURLの設定
ユーザーが認証を許可した後に、リダイレクトされるURLを指定します。
LINEログイン設定のタブに移動してコールバックURLを入力し更新を押してください。
尚、この記事では後のルーティングで`auth/line/callback`というパスを指定するので、記事通りに進めるのであれば、
`https://各自のドメイン/auth/line/callback`で設定しておいて下さい。
![スクリーンショット 2020-09-16 11.55.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/f27108e9-59e5-0e69-318b-d2c4b16d654e.png)


#メールアドレスを取得する設定
ログインしたユーザーのメールアドレスを取得するための設定です。必要な場合のみ設定して下さい。
チャネル基本設定のタブの下の方に「OpenID Connect」という項目があります。
申請ボタンを押すと、申請条件への同意と、メールアドレスの取得と利用についてユーザーに提示する文面のスクリーンショットのアップロードを求められるので済ませます。
![スクリーンショット 2020-09-15 22.17.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546124/a20dc0e6-1dae-9e09-8388-4aa55dbc9fec.png)

#必要なライブラリの準備
・Guzzle
リクエストを送信する際に使用します。
[公式](http://docs.guzzlephp.org/en/stable/index.html)

```terminal
$ composer require guzzlehttp/guzzle
````

#Laravel
ここからコードを書いていきます。

##設定
・チャネルID
・チャネルシークレット
・コールバックURL
は`.env`に書いておき、config経由で呼び出すことにします。


```:.env
LINE_CHANNEL_ID=あなたのチャネルid
LINE_CHANNEL_SECRET=あなたのチャネルシークレット
LINE_CALLBACK_URL=設定したコールバックURL
```

configディレクトリにline.phpというファイルを作り、envから値を受け取るように編集します。

```php:config/line.php
<?php
return [
    'client_id' => env('LINE_CHANNEL_ID'),
    'client_secret' => env('LINE_CHANNEL_SECRET'),
    'callback_url' => env('LINE_CALLBACK_URL'),
];
```

##ルーティング
`routes/web.php`を以下のように設定します。

```php:routes/web.php
// LINEの認証画面に遷移
Route::get('auth/line', 'Auth\LineOAuthController@redirectToProvider')->name('line.login');
// 認証後にリダイレクトされるURL(コールバックURL)
Route::get('auth/line/callback', 'Auth\LineOAuthController@handleProviderCallback');
```

##view
この記事ではURLに遷移させるだけの単純なものを書いておきます。
本来は、公式の指定するデザインのログインボタンを設定する必要があります。
[LINEログインボタン デザインガイドライン](https://developers.line.biz/ja/docs/line-login/login-button/)

```php
<a href="{{ route('line.login') }}">LINEでログイン</a>
```

##コントローラー
以下で作成

```terminal
$ php artisan make:controller Auth/LineOAuthController
```

編集します。

```php:app/Http/Controllers/Auth/LineOAuthController.php 
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use GuzzleHttp\Client;

class LineOAuthController extends Controller
{
    private const LINE_OAUTH_URI = 'https://access.line.me/oauth2/v2.1/authorize?';
    private const LINE_TOKEN_API_URI = 'https://api.line.me/oauth2/v2.1/';
    private const LINE_PROFILE_API_URI = 'https://api.line.me/v2/';
    private $client_id;
    private $client_secret;
    private $callback_url;

    public function __construct() {
        $this->client_id = Config('line.client_id');
        $this->client_secret = Config('line.client_secret');
        $this->callback_url = Config('line.callback_url');
    }

    public function redirectToProvider()
    {
        $csrf_token = Str::random(32);
        $query_data = [
            'response_type' => 'code',
            'client_id' => $this->client_id,
            'redirect_uri' => $this->callback_url,
            'state' => $csrf_token,
            'scope' => 'profile openid',
        ];
        $query_str = http_build_query($query_data, '', '&');
        return redirect(self::LINE_OAUTH_URI . $query_str);
    }

    public function handleProviderCallback(Request $request)
    {
        $code = $request->query('code');
        $token_info = $this->fetchTokenInfo($code);
        $user_info = $this->fetchUserInfo($token_info->access_token);
        //  ログイン処理
    }

    private function fetchUserInfo($access_token)
    {
        $base_uri = ['base_uri' => self::LINE_PROFILE_API_URI];
        $method = 'GET';
        $path = 'profile';
        $headers = ['headers' => 
            [
                'Authorization' => 'Bearer ' . $access_token
            ]
        ];
        $user_info = $this->sendRequest($base_uri, $method, $path, $headers);
        return $user_info;
    }

    private function fetchTokenInfo($code)
    {
        $base_uri = ['base_uri' => self::LINE_TOKEN_API_URI];
        $method = 'POST';
        $path = 'token';
        $headers = ['headers' => 
            [
                'Content-Type' => 'application/x-www-form-urlencoded'
            ]
        ];
        $form_params = ['form_params' => 
            [
                'code'          => $code,
                'client_id' => $this->client_id,
                'client_secret' => $this->client_secret,
                'redirect_uri'  => $this->callback_url,
                'grant_type'    => 'authorization_code'
            ]
        ];
        $token_info = $this->sendRequest($base_uri, $method, $path, $headers, $form_params);
        return $token_info;
    }

    private function sendRequest($base_uri, $method, $path, $headers, $form_params = null)
    {
        try {
            $client = new Client($base_uri);
            if ($form_params) {
                $response = $client->request($method, $path, $form_params, $headers);
            } else {
                $response = $client->request($method, $path, $headers);
            }
        } catch(\Exception $ex) {
            //　例外処理
        }
        return json_decode($response->getbody()->getcontents());
    }
}
```

アクション名が思いつかなかったのでSociliteから流用して使ってます。

handleProviderCallback（）の変数、`$token_info`、`$user_info`には、

- `$token_info` →　トークン情報（アクセストークン、リフレッシュトークンなど）
- `$user_info` →　ユーザー情報（ユーザーID、ユーザー名、プロフィール画像、プロフィールメッセージ）

が入っていますので、これらを各自のテーブル構成に合わせて保存しログインさせて下さい。
尚、アクセストークンの有効期間は30日ですので、適宜リフレッシュトークンを使って新しいアクセストークンを取得して下さい。

#一言
ソーシャルログインはSocialiteを使ったTwitterログインしか実装経験はありませんでしたが、LINEでも意外と簡単にできて驚きでした。特に鬼門はないかと思います。
何か間違えなどがあればコメントにお願いいたします。


#参考
[LINE公式 ウェブアプリにLINEログインを組み込む](https://developers.line.biz/ja/docs/line-login/integrate-line-login/#%E5%A7%8B%E3%82%81%E3%82%8B%E5%89%8D%E3%81%AB)
[LINE公式 ユーザープロフィールを取得する](https://developers.line.biz/ja/reference/social-api/#%E3%83%95%E3%82%9A%E3%83%AD%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB)