Lumen8でJWT認証を行う

Table of Content

広告

はじめに

LumenとはLaravelベースのマイクロフレームワークというPHPフレームワークです。
Laravelよりは動作が軽いので、APIサーバの用途に適したフレームワークです。

Laravelから機能を落としているので、Laravelとイコールではありませんが、軽量フレームワークのSlimよりはフルスタックフレームワークの分類にあたるのでは無いでしょうか。

公式サイトは以下の通りです。

LaravelとLumenの違いは以下のサイトが参考になるかと思います。

Laravelは「Laravel Sanctum」を追加することによってAPI認証がサポートされます。
しかし、Lumenでは「Laravel Sanctum」は使用することができません。

本記事は、LumenにJWTを追加して、API認証機能をサポートさせた全記録です。

Lumenのバージョンは以下で構築しました。

$ php artisan --version
Laravel Framework Lumen (8.2.1) (Laravel Components ^8.0)

Lumen Frameworkインストール

Lumenの構築は「VSCode Remote ContainersでLaravel開発環境を構築」の環境で行っています。

まずは、Lumenをインストールします。

composer create-project --prefer-dist laravel/lumen .

Laravelだと「.env」のAPP_KEYを設定してくれますが、Lumenは設定されません。
また、「php artisan key:generate」も動きません。

以下のコマンドを入力し、結果を「.env」のAPP_KEYに設定します。

php -r "echo base64_encode(random_bytes(24)).PHP_EOL;"

「http://localhost/」 にアクセスすると、次のような画面が表示されればLumenのインストールは完了です。

Lumen

JWT関連のパッケージインストール

JWTは採用例が多かった「tymon/jwt-auth」を採用しました。また、「config」ディレクトリを使用するので「chuckrincon/lumen-config-discover」もインストールします。

composer require chuckrincon/lumen-config-discover
mkdir config
composer require tymon/jwt-auth

「bootstrap/app.php」を次の様に編集します。

  • EloquentとFacadesを有効にします
  • Authenticationを登録します
  • App,Auth,Event,Tymon\JWTAuth,Chuckrincon\LumenConfigDiscoverサービスプロパイダを登録します。
$app = new Laravel\Lumen\Application(
    dirname(__DIR__)
);

$app->withFacades(); // コメントを外す
$app->withEloquent(); //コメントを外す

// コメントを外す
$app->routeMiddleware([
     'auth' => App\Http\Middleware\Authenticate::class,
]);

$app->register(App\Providers\AppServiceProvider::class);   // コメントを外す
$app->register(App\Providers\AuthServiceProvider::class);  // コメントを外す
$app->register(App\Providers\EventServiceProvider::class); // コメントを外す
$app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class); // 追加する
$app->register(Chuckrincon\LumenConfigDiscover\DiscoverServiceProvider::class); // 追加する

次のコマンドで.envにJWT_SECRETキーを設定します。

$ php artisan jwt:secret
jwt-auth secret [gmqUWErZzlIMqhUjTW6mHTh03WHrMDHwRAUDP2cAOA8mJW1sAqgiq7oyxFLjPM0D] set successfully.

.envにJWT_SECRETが追加されているのを確認してください。

JWT_SECRET=gmqUWErZzlIMqhUjTW6mHTh03WHrMDHwRAUDP2cAOA8mJW1sAqgiq7oyxFLjPM0D

config/auth.phpを追加

「config/auth.php」を次のように追加します。

<?php

return [
     'defaults' => [
         'guard' => 'api',
         'passwords' => 'users',
     ],

     'guards' => [
         'api' => [
             'driver' => 'jwt',
             'provider' => 'users',
         ],
     ],

     'providers' => [
         'users' => [
             'driver' => 'eloquent',
             'model' => \App\Models\User::class
         ]
     ]
];

ユーザテーブル作成

LaravelではUserテーブルのマイグレーションファイルは標準でありましたが、Lumenにはありません。
以下のコマンドでマイグレーションファイルを生成します。

php artisan make:migration create_users_table

「database/migrations/yyyy_mm_dd_hhmmss_create_users_table.php」を次のように編集します。

<?php
// ~
class CreateUserTable
{
     // ~
     public function up()
     {
         Schema::create('users', function (Blueprint $table) {
             $table->increments('id');
             $table->string('name');
             $table->string('email')->unique();
             $table->string('password');
             $table->timestamps();
         });
     }
     // ~
}

マイグレーションを実行します。

php artisan migrate

「app/Models/User.php」をjwt-authに対応させるよう編集します。

<?php

namespace App\Models;

use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Lumen\Auth\Authorizable;
use Tymon\JWTAuth\Contracts\JWTSubject; // 追加

class User extends Model implements AuthenticatableContract, AuthorizableContract, JWTSubject // JWTSubjectを追加
{
    use Authenticatable, Authorizable, HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email',
    ];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [
        'password',
    ];

    // 以下を追加
    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     * 
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

   /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}

認証用コントローラの作成

コントローラ「app/Http/Controllers/AuthController.php」を以下のように作成します。

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use App\Models\User;

class AuthController extends Controller
{
     /**
      * ユーザの登録
      *
      * @param  Request $request
      * @return Response
      */
     public function register(Request $request)
     {
        $this->validate($request, [
            'name'     => 'required',
            'email'    => 'required|email|unique:users',
            'password' => 'required|min:6|confirmed',
        ]);

        $user = new User;
        $user->name     = $request->input('name');
        $user->email    = $request->input('email');
        $user->password = app('hash')->make($request->input('password'));
        $user->save();

        return response()->json(['message' => 'created'],201);
     }

     /**
      * ユーザ認証
      *
      * @param  Request $request
      * @return Response
      */
      public function login(Request $request)
      {
          $this->validate($request, [
              'email'    => 'required',
              'password' => 'required',
          ]);

          $credentials = $request->only(['email', 'password']);

          if(! $token = auth()->attempt($credentials)){
              return response()->json(['message' => '$credentials'], 401);
          }

          return response()->json(['token' => $token], 200);
      }

    /**
     * ログインユーザの表示
     * 
     * @return Response
     */
    public function me()
    {
        return response()->json(auth()->user(), 200);
    }
}

※Lumenは「php artisan make:controller 〜」はありません。

ルーティング定義

ルーティング「routes/web.php」を以下の通り編集します。

$router->get('/', function () use ($router) {
    return $router->app->version();
});

$router->group(['prefix' => 'api'], function() use($router){
    $router->post('register', 'AuthController@register');
    $router->post('login', 'AuthController@login');
    $router->group(['middleware' => 'auth:api'], function() use($router){
        $router->get('me', 'AuthController@me');
    });
});

実装を検証してみる

検証はpostmanでAPIをリクエストすることで行います。

まずはユーザ情報の登録を行います。

ユーザ登録

ユーザテーブルが作成されたのが確認できます。

ユーザテーブル

ログインを行いトークンを得ます。

ログイン

得られたトークンを「Authorization」の「Bearer Token」に貼り付けてAPIを送信します。

認証済

トークンが正しくない場合はステータス401で返ってきます。

未認証

最後に

Laravelでは大げさだなぁ…なシステムの構築にはLumenは選択肢としてありだと思います。
しかし、認証周りをしっかり作りたい(2要素認証やEmail Verification等)のであれば、Laravelを選択したほうが良いかと思います。

今回作成したプロジェクト一式はgithubに上げておきました。

参考記事