Sub Documents

Laravelのログイン状態をWordPressで読み込んで自動ログインする


 別システムのログイン状態を維持するためには、最近ではログインのみのアプリケーションを作成してそれぞれのシステムでAPI連携でいろいろやるっていうのがあります。それと似たような仕組みではありますが、ここではLaravel側で認証トークンを発行して、そのトークンでWordpress側でもログインするというような仕組みを作ります。
 仕組みとしては、トークンを一回のログイン時に一度だけ発行し、再度ログインするときにはもう一度違うトークンを発行します。通常ワンタイム・トークンとか呼ばれている仕組みです。
 正直いうと二重でログイン状態を保つので面倒くさい作りにはなってしまいます。大規模なシステムではやらない方がよいです。

  1. Laravelのユーザーデータでトークンを生成する
  2. WordPressでそのトークンを使って認証をする

という順序になります。ここではLaravelですでにログイン機構がすでに作成されていることを前提にして進めています。

Laravelでトークンを発行する

usersテーブルにlogin_tokenというカラムを追加します。これは通常のマイグレーションで行いましょう。マイグレーションの具体的な方法は端折ります。

// database/migrations/2014_10_12_000000_create_users_table.php
...
$table->string('login_token')->nullable();
...

このトークンはログインしたときに発行、ログアウト時に削除という機能になるので、Laravelのイベントリスナーの設定をします。LogAuthenticatedがログイン時、LogSuccessfulLogoutがログアウト時のリスナーになります。

//app/Providers/EventServiceProvider.php
...
protected $listen = [
  Registered::class => [
      SendEmailVerificationNotification::class,
  ],
  ...
  'Illuminate\Auth\Events\Authenticated' => [
      'App\Listeners\LogAuthenticated',
  ],
  'Illuminate\Auth\Events\Logout' => [
      'App\Listeners\LogSuccessfulLogout',
  ],
];
...

次のコマンドを実行してイベントリスナーを作成します。

$ ./vendor/bin/sail artisan event:generate

すると、app/Listenersフォルダの中に以下2つのファイルが作成されていますので、これら2つのファイル内にコードを書き込んでいきます。

app/Listeners/LogAuthenticated.php
app/Listeners/LogSuccessfulLogout.php

ログイン時の挙動の記述

// app/Listeners/LogAuthenticated.php

use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
...
public function handle(Authenticated $event)
{
  $user = $event->user;
  $user->login_token = $user->id .'-'. Str::random(64);
  $user->save();
}
...

ログアウト時の挙動の記述

// app/Listeners/LogSuccessfulLogout.php

public function handle(Logout $event)
{
  $user = $event->user;
  $user->login_token = null;
  $user->save();
}

これで、ユーザーログイン時にトークンが発行されていたらOKです。
ユーザーがログインしている場合は、Laravelではauth()->user()でログインしているユーザーの情報が取得できるので、トークンもここで取得できるようになっています。bladeやコントローラの中で確認してみましょう。

// blade
{{ auth()->user()->login_token }}

WordPressでそのトークンを使って認証をする

今度はWordpress側でこのトークンを受け取って認証情報を確認できるようにします。仕組みとしてはLaravelからGET値でトークンを受け取って、Laravelのusersテーブルで認証しているかどうかを確認するという方式です。
Wordpress側にユーザーがいたらそのユーザーで認証し、いなかったら、新たにユーザーを作成して認証、ログインします。
LaravelのDB情報をwp-configに設定します。

define('LARAVEL_DB_TYPE', 'mysql');
define('LARAVEL_DB_HOST', 'localhost');
define('LARAVEL_DB_PORT', '3306');
define('LARAVEL_DB_DATABASE', 'YOUR-LARAVEL-DB-NAME');
define('LARAVEL_DB_USERNAME', 'YOUR-USER-NAME');
define('LARAVEL_DB_PASSWORD', 'YOUR-PASSWORD');
define('LARAVEL_DB_USER_TABLE', 'users');

次にfunctions.phpでトークンを受け取ってwordpress上で有効にします。

<?php
/**
 * Laravel login to WordPress
 */
add_filter('wp', 'laravel_login');

function laravel_login() {

  global $wp;

  # access tokenの取得
  $token = @htmlentities($_GET['token'], ENT_QUOTES);

    if (!empty($token)) {

      try {

      $pdo = new PDO(
          LARAVEL_DB_TYPE . ':' .
          'host=' . LARAVEL_DB_HOST . ';' .
          'port=' . LARAVEL_DB_PORT . ';' .
          'dbname=' . LARAVEL_DB_DATABASE . ';' .
          'charset=utf8mb4', // DSN
          LARAVEL_DB_USERNAME,
          LARAVEL_DB_PASSWORD
      );

      } catch (Exception $e) {
        throw new Exception('Failed to connect to database.');
      }

      // var_dump($pdo);
      $sql = $pdo->prepare(
        'SELECT 
          id,
          name,
          email,
          login_token,
          created_at, updated_at FROM ' . LARAVEL_DB_USER_TABLE . ' WHERE login_token=:token'
      );
      $sql->bindParam(':token', $token);
      $sql->execute();
      $laravel_user = $sql->fetch();

      if (!empty($laravel_user)) {

        // wordpress user登録の準備
        $laravel_name = $laravel_user['name'];
        $laravel_email = $laravel_user['email'];
        $password = md5(uniqid(rand(), 1));

        $user_id = null;
        $wp_user = get_user_by('email', $laravel_email);

        if (!$wp_user) { // ユーザー登録

          $user_id = wp_insert_user([
            'user_login' => md5(uniqid(rand(), 1)),
            'user_pass' => $password,
            'user_email' => $laravel_email,
            'display_name' => $laravel_name,
            'role' => 'author'
          ]);

        } else { // ユーザー更新

          $user_id = $wp_user->ID;
          wp_update_user([
            'ID' => $user_id,
            'user_pass' => $password,
            'user_email' => $laravel_email,
            'display_name' => $laravel_name
          ]);

        }

        // ユーザーIDを使ってログイン
        wp_set_current_user($user_id);
        wp_set_auth_cookie($user_id);

      } else {
        wp_redirect('https://example.com/'); // ご自身のURLへ変更してください  
        exit();
      } 
    } else {
        die('user has no token.');
    }
  return 1;
}

 若干解説しておきます。最初にLaravelから渡ってきたワンタイムトークンをGET値から取得しておきます。DBに接続してLaravel側のusersから情報を取得できるようにしておいて、ワンタイムトークンを検索します。およそ重複が生じないようにトークンを設定しています(Str::random(64);)ので問題はないでしょう。重複しないユーザーIDをプレフィックスにしているので大規模なシステムでも問題はないはず。
 トークンが存在すれば(Laravel側でログインしていれば)Wordpress側のユーザーに同じEmailがないか探します。あれば更新、なければ新規で新しいユーザーを作成してログイン状態にします。
 ログイン状態のステータスはwordpressの関数is_user_logged_in()から取得できます。

if (is_user_logged_in() == true) {
echo 'ログイン中';
}

 この方法はEmailで同期させています。もしWordpress側に同じEmailのユーザーがいたら、それでログインします。Wordpress側のdisplay_nameにはLaravel側のnameを使用しています。LaravelもWordpressもデフォルトでEmailアドレスはユニークにしているので、これで問題はないでしょう。またWordpress側にユーザーが存在する際もパスワードやIDを更新してしまうので、Laravel側から必ずログインしてください。Wordpress側のログイン画面からはログインできません。

ログイン・ログアウト

ログインに関してはLaravel側からログインさせて、Wordpressに移動する際にはトークンを渡してあげればOKです。そしてログアウトの際は少々やっかいで、Wordpress側でログアウトすると Laravel側のログインが残るので、基本的にはLaravel側からログアウトさせるのがよいということになります。しかしWordpress側からLaravelのログアウトを直接操作するにはいくつかの方法がありますが、ここではLaravelのログアウト用のルーティングをひとつ作成してアンカー要素による方法を作成します。

<a class="dropdown-item" href="/logout">ログアウト</a>

Laravel側でのログアウトはPOSTなので、以下のエラーが出ます。

Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
The GET method is not supported for this route. Supported methods: POST.

これを追加します。

//app/routes/web.php
Route::get('/logout', [App\Http\Controllers\Auth\LoginController::class, 'logout'])->name('logout');

コントローラを修正します。

//app/Http/Controllers/Auth/LoginController.php
use Illuminate\Support\Facades\Auth;
...
    protected function logout(Request $request) {
        Auth::logout();
        return redirect('login');
    }
...