Laravel8でLINEログインを実装する

# はじめに
Laravel8でLINEログインの実装を行ったので備忘録として残しておきます。

# 環境
PHP 7.4.27
Laravel 8.78.1

#事前準備
LINEログインの機能を使用するには[LINE Developersコンソール](https://developers.line.biz/console/)でプロバイダー及びLINEログインのチャネルを作成しておく必要があります。

下記のドキュメントを参考に作成していきますが詳細は割愛します。

https://developers.line.biz/ja/docs/line-login/getting-started/

チャネルを作成することでチャネルID、チャネルシークレットを取得できます。
コールバックURLも忘れずに設定しておきましょう。

#実装
LINEログインの実装部分に焦点を当てているためLaravelプロジェクトの作成や通常のログインなどの認証関連のパッケージの導入は省略しています。

### Laravel Socialiteをインストール
今回はLaravelの公式パッケージであるLaravel Socialiteを使用して実装していきます。
Socialiteを使用することで外部サービスのアカウントによるログイン機能をよりシンプルに実装できます。

https://readouble.com/laravel/8.x/ja/socialite.html

下記のコマンドを実行してSocialiteをインストールします。

```
composer require laravel/socialite
```

公式パッケージがサポートしている外部サービスにLINEが含まれていないので下記サイトで別途プロバイダーの追加方法を確認します。

https://socialiteproviders.com/

LINEプロバイダーを追加するには下記のコマンドを実行します。

```
composer require socialiteproviders/line
```

### 環境変数の設定
事前準備で取得したチャネルID、チャネルシークレットと設定したコールバックURLを`.env`ファイルに設定します。

```
LINE_CLIENT_ID='取得したチャネルID'
LINE_CLIENT_SECRET='取得したチャネルシークレット'
LINE_REDIRECT_URI='(アプリのURL)/login/line/callback'
```

configで設定しておきます。

```php:config/services.php
'line' => [    
    'client_id' => env('LINE_CLIENT_ID'),  
    'client_secret' => env('LINE_CLIENT_SECRET'),  
    'redirect' => env('LINE_REDIRECT_URI') 
],
```

### イベントを追加
Socialiteに別途プロバイダーを追加している場合は`app/Providers/EventServiceProvider`へ追加の記述をします。
Laravel8からは記述方法が変わっています。

```php:app/Providers/EventServiceProvider.php
protected $listen = [
    // 下記3行を追加
    \SocialiteProviders\Manager\SocialiteWasCalled::class => [
        \SocialiteProviders\Line\LineExtendSocialite::class.'@handle',
    ],
];
```

### usersテーブルの変更
LINEログインしたユーザーから取得したプロバイダーIDを保存するために`users`テーブルを変更します。
メールアドレスによるログイン方法も残すには`email`と`password`でnullを許容しておきます。

```php:database/migrations/2014_10_12_000000_create_users_table.php
public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique()->nullable(); // 変更
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password')->nullable(); // 変更
        $table->string('line_user_id')->unique()->nullable(); // 追加
        $table->rememberToken();
        $table->timestamps();
    });
}
```

変更したらマイグレーションを実行してテーブルを作成しておきましょう。

```
php artisan migrate
```

### Userモデルの変更
usersテーブルを変更したのでUserモデルもあわせて変更します。

```php:app/Models/User.php
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var string[]
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'line_user_id' // 追加
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'line_user_id' // 追加
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}
```

### ルーティングを追加
次にLINEログインに関わる処理のルーティングを追加します。
認証関連のパッケージを導入したときにログイン用のコントローラーが作成されているので利用します。

```php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Auth\LoginController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Auth::routes();
Route::prefix('login')->name('login.')->group(function() {
    Route::get('/line/redirect', [LoginController::class, 'redirectToProvider'])->name('line.redirect');
    Route::get('/line/callback', [LoginController::class, 'handleProviderCallback'])->name('line.callback');
});
```

### コントローラーの実装
LINEログインによる認証処理を実装していきます。
LINEログイン後のページの遷移先は通常のログイン時と同じにしています。

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

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    /**
     * 外部サービスの認証ページへリダイレクトする。
     */
    public function redirectToProvider() {
        return Socialite::driver('line')->redirect();
    }

    /**
     * 外部サービスからユーザー情報を取得し、ログインする。
     */
    public function handleProviderCallback(Request $request) {
        $line_user = Socialite::driver('line')->user();

        $user = User::firstOrCreate(
            ['line_user_id' => $line_user->id],
            ['name' => $line_user->name]
        );

        $this->guard()->login($user, true);
        return $this->sendLoginResponse($request);
    }
}
```

`redirect`メソッドの前に`setScopes`メソッドや`with`メソッドを使用することで取得するユーザー情報の範囲を指定したり認証のオプションを指定することができます。
指定できるパラメータは以下のURLで確認できます。
なお`with`メソッドを使用するときは`state`や`response_type`などの必須パラメータは指定しないようにします。

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

```php
public function redirectToProvider() {
    $nonce_token = Str::random(40);

    return Socialite::driver('line')
        ->setScopes(['openid', 'profile'])
        ->with([
            'nonce' => $nonce_token,
        ])
        ->redirect();
}
```

### ビューの作成
ここまで実装できればあとは、LINEログイン画面へ遷移するためのリンクを設置するだけです。
この記事ではただのテキストリンクにしていますが、本来は公式でLINEログインボタンのデザインが指定されています。

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

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

#まとめ
Socialiteを使用することで比較的簡単にLINEログインを実装することができました。
初めは古いバージョンのLaravelの記事を参考にしていたので一部躓いたところがありましたが、公式ドキュメントを確認しながらなんとか解決できました。
間違いや改善点などがあればご教示いただけますと幸いです。