Laravel SocialiteをベースにしてLaravelにLINE loginを実装するための手順

LaravelにはオフィシャルパッケージとしてSocialiteというのが用意されています。これを使うとFacebook, Google, linkedIn, GitHub, BitbucketといったサービスでのOAuth認証を簡単に実装してくれます。プロバイダーとして使えるサービスはこれ以外にもたくさんあって、Socialite Providersにいくと有名どころのサービスのプロバイダーがもれなく用意されています。残念ながらLINEのOAuth認証がこのリストになかったので、自分で用意することになりました。ということでLaravelにLINE loginを実装する手順をまとめてみました。ここではLaravel側の手順を説明するため、LINE側でのビジネスアカウントの作成、チャンネルの作成、LINE Loginの有効化までは終わっているものとします。

Laravelプロジェクトの作成

Laravelがないと始まらないので、globalにインストールしたlaravelをnewします;

laravel new example

Laravel SocialiteのインストールとLINE用アダプターの追加

composer requireでSocialiteを追加します;

composer require laravel/socialite

composer requireが終わったら、SOcialiteにLINE用のアダプターを追加します。まずはvendor/laravel/socialite/src/SocialiteManager.phpに以下のプライベートメソッドを実装します;

/**
 * Create an instance of the specified driver.
 *
 * @return \Laravel\Socialite\Two\AbstractProvider
 */
protected function createLineDriver()
{
    $config = $this->app['config']['services.line'];
 
    return $this->buildProvider(
        'Laravel\Socialite\Two\LineProvider', $config
    );
}

さらにvendor/laravel/socialite/src/Two/LineProvider.phpを以下の内容で作成します;

<?php
 
namespace Laravel\Socialite\Two;
 
use Exception;
use Illuminate\Support\Arr;
 
class LineProvider extends AbstractProvider implements ProviderInterface
{
    /**
     * The scopes being requested.
     *
     * @var array
     */
    protected $scopes = [];
 
    /**
     * {@inheritdoc}
     */
    protected function getAuthUrl($state)
    {
        return $this->buildAuthUrlFromBase('https://access.line.me/dialog/oauth/weblogin', $state);
    }
 
    /**
     * {@inheritdoc}
     */
    protected function getTokenFields($code)
    {
        return [
            'grant_type' => 'authorization_code',
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret,
            'code' => $code,
            'redirect_uri' => $this->redirectUrl,
        ];
    }
 
    /**
     * {@inheritdoc}
     */
    protected function getTokenUrl()
    {
        return 'https://api.line.me/v1/oauth/accessToken';
    }
 
    /**
     * {@inheritdoc}
     */
    protected function getUserByToken($token)
    {
        $userUrl = 'https://api.line.me/v1/profile';
 
        $response = $this->getHttpClient()->get(
            $userUrl, $this->getRequestOptions($token)
        );
 
        $user = json_decode($response->getBody(), true);
 
        return $user;
    }
 
    /**
     * {@inheritdoc}
     */
    protected function mapUserToObject(array $user)
    {
        return (new User)->setRaw($user)->map([
            'id' => $user['mid'],
            'name' => $user['displayName'],
            'nickname' => $user['displayName'],
            'email' => null,
            'avatar' => $user['pictureUrl'],
            'avatar_original' => $user['pictureUrl'],
        ]);
    }
 
    /**
     * Get the default options for an HTTP request.
     *
     * @return array
     */
    protected function getRequestOptions($token)
    {
        return [
            'headers' => [
                'Authorization' => 'Bearer '.$token,
            ],
        ];
    }
}

Socialite側の修正は以上です。

Laravel SocialiteをLaravel側で有効化させる

次はSocialiteのLaravel側の設定です。config/app.phpにSocialiteServiceProviderを登録します;

'providers' => [
    // Other service providers...
 
    Laravel\Socialite\SocialiteServiceProvider::class,
],

同じファイルにSocialiteファサードをaliases配列に追加します;

'aliases' => [
    // Other aliases...
 
    'Socialite' => Laravel\Socialite\Facades\Socialite::class,
],

.envにLINE LoginのChannel ID, Channel Secret, Callback URLを追加します;

LINE_CHANNEL_ID=[Channel ID]
LINE_CHANNEL_SECRET=[Channel Secret]
LINE_CALLBACK_URL=[Callback URL]

[Channel ID], [Channel Secret], [Callback URL]には取得したものを入力してください。

config/services.phpにクレデンシャルを追加します;

'line' => [
    'client_id' => env('LINE_CHANNEL_ID'),
    'client_secret' => env('LINE_CHANNEL_SECRET'),
    'redirect' => env('LINE_CALLBACK_URL'),
],

以上でSocialiteでのLINEログイン実装の準備が終わりました。

デフォルトのモデルの修正

最低限の実装をするために、デフォルトで用意されているapp/User.phpのUserモデルを修正します;

protected $fillable = [
    'name', 'mid', 'avatar',
];
 
...
 
protected $hidden = [
    'mid', 'remember_token',
];

マイグレーションもあらかじめ用意されているdatabase/migrations/2014_10_12_000000_create_users_table.phpを修正します;

public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->string('mid')->unique();
        $table->string('avatar');
        $table->rememberToken();
        $table->timestamps();
    });
}

モデルとマイグレーションの修正が終わったら、データベースを用意してmigrateします;

php artisan migrate

これでデータベース内にしかるべきテーブルが作成されます。

コントローラの追加

モデルの次はコントローラにルーターを追加します。OAuth providerへリダイレクトするためのルーターと、認証された後にコールバックを受け取るルーターの二つが必要となります。app/Http/Controllers/AuthController.phpに以下の内容を記述します;

<?php
 
namespace App\Http\Controllers\Auth;
 
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Socialite;
 
class AuthController extends Controller
{
    /**
     * Redirect the user to the LINE authentication page.
     *
     * @return Response
     */
    public function redirectToProvider()
    {
        return Socialite::driver('line')->redirect();
    }
 
    /**
     * Obtain the user information from LINE.
     *
     * @return Response
     */
    public function handleProviderCallback()
    {
        try {
            $user = Socialite::driver('line')->user();
        } catch (Exception $e) {
            return redirect()->intended('/');
        }
 
        $authUser = $this->findOrCreateUser($user);
        Auth::login($authUser, true);
 
        return redirect()->intended('dashboard');
    }
 
    /**
     * Logout
     *
     * @return Response
     */
    public function logout()
    {
        Auth::logout();
        return redirect()->intended('/');
    }
 
    /**
     * Return user if exists; create and return if doesn't
     *
     * @param object $user
     * @return User
     */
    private function findOrCreateUser($user)
    {
        if ($authUser = \App\User::where('mid', $user->id)->first()) {
            return $authUser;
        }
 
        return \App\User::create([
            'mid' => $user->id,
            'name' => $user->name,
            'avatar' => $user->avatar
        ]);
    }
}

ログアウト用の処理も追加してあります。上記のログイン処理、ログイン後のコールバック処理に加えて、ログイン後のダッシュボード用のコントローラも用意します;

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
 
class DashboardController extends Controller
{
 
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }
 
    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('dashboard');
    }
}

コンストラクタでauth middlewareを呼び出すことで、ログインしていないユーザがdashboardにアクセスできないようにしています。

ルーターの追加

routes/web.phpにルーターを追加します。

Route::get('/', function () {
    return view('welcome');
});
 
Route::get('dashboard', 'DashboardController@index');
 
Route::get('login', 'Auth\AuthController@redirectToProvider')->name('login');
Route::get('callback', 'Auth\AuthController@handleProviderCallback');
Route::post('logout', 'Auth\AuthController@logout')->name('logout');

ビューの作成

最後にビューを作成します。とりあえず最低限ということで、まずはログインボタンを設置するトップページとして、デフォルトのwelcome.blade.phpを触ります;

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>LINE Login</title>
        <link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">
        <style>
            html, body {
                background-color: #fff;
                color: #636b6f;
                font-family: 'Raleway', sans-serif;
                font-weight: 100;
                height: 100vh;
                margin: 0;
            }
            .full-height {
                height: 100vh;
            }
            .flex-center {
                align-items: center;
                display: flex;
                justify-content: center;
            }
            .position-ref {
                position: relative;
            }
            .top-right {
                position: absolute;
                right: 10px;
                top: 18px;
            }
            .links > a {
                color: #636b6f;
                padding: 0 25px;
                font-size: 12px;
                font-weight: 600;
                letter-spacing: .1rem;
                text-decoration: none;
                text-transform: uppercase;
            }
        </style>
    </head>
    <body>
        <div class="flex-center position-ref full-height">
            @if (Route::has('login'))
                <div class="top-right links">
                    @if (Auth::check())
                        <a href="{{ url('/dashboard') }}">Dashboard</a>
                    @endif
                </div>
            @endif
            <div class="title m-b-md">
                <a href="{{ url('/login') }}"><img src="images/btn_login_base.png"></a>
            </div>
        </div>
    </body>
</html>

LINEのログインボタン用の画像はLINEの開発者ページからダウンロードしてきて、public/imagesディレクトリを作成して画像ファイルを入れておいてください。

もう一つ必要なのが、ログイン後のダッシュボード画面です。これも最低限ということでこんな感じです;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name', 'Laravel') }}</title>
    <link href="/css/app.css" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-default navbar-static-top">
            <div class="container">
                <div class="navbar-header">
                    <a class="navbar-brand" href="{{ url('/') }}">
                        {{ config('app.name', 'Laravel') }}
                    </a>
                </div>
                <div class="nav navbar-nav navbar-right">
                    <form style="padding:7px 0" action="{{ url('/logout') }}" method="POST">
                        {{ csrf_field() }}
                        <button class="btn btn-link" type=submit>Logout</button>
                    </form>
                </div>
            </div>
        </nav>
        <div class="container">
            <div class="row">
                <div class="col-md-8 col-md-offset-2">
                    <div class="panel panel-default">
                        <div class="panel-heading">Dashboard</div>
 
                        <div class="panel-body">
                            Logged in as {{ Auth::user()->name }}!
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

ログインしてみる

php artisan serve

でローカルサーバを立ち上げて、ブラウザでhttp://localhost:8000へアクセスしたら、LINE loginボタンをクリックしてください。LINEのログイン画面が現れるので、メールアドレスとパスワードを入力してLOGINをクリックするとhttp://localhost:8000/dashboardへ飛んでダッシュボードが表示されると思います。