[Laravel5.5でREST API + SPA] JWTAuthでのAPI認証にRole機能を追加する

ユーザの種類によって許可するメソッドを分けたい

ユーザの種類って1種類だけってことはまずないですよね。
少なくとも一般ユーザと管理者の2種類は必要になるケースがほとんどだと思います。
そういった場合に必要なRole機能をLaravel + JWTAuthで実装する方法をご紹介します。

関連記事 : [Laravel5.5でREST API + SPA] API認証を実装する (JWTAuth) バックエンド編

ちなみに、JWTAuthでのAPI認証自体の記事はけっこう数がありますが、
そこにRole機能をプラスする方法を紹介している記事は見当たりませんでした。
この記事がお役に立てれば幸いです。

 

JWTAuthがやっている処理を確認してみる

Route::group(['middleware' => 'api'], function () {
    Route::group(['middleware' => ['jwt.auth']], function() {
        Route::resource('users', 'UsersController');
        Route::get('me',  'AuthenticateController@getCurrentUser');
        Route::get('logout',  'AuthenticateController@logout')->middleware('jwt.refresh');
    });
    Route::post('authenticate',  'AuthenticateController@authenticate');
});

APIのルーティングのここでJWTAuthをミドルウェアとして噛ませているわけですね。

このjwt.authというエイリアスは
以下のようにKernel.phpでvendorのクラスと紐づけられています。

protected $routeMiddleware = [
・・・
    'jwt.auth' => \Tymon\JWTAuth\Http\Middleware\Authenticate::class,
    'jwt.refresh' => \Tymon\JWTAuth\Http\Middleware\RefreshToken::class,
];

ここですね。
Tymon\JWTAuth\Http\Middleware\Authenticate::class
が登録されています。
その中身が以下。

<?php
namespace Tymon\JWTAuth\Http\Middleware;

use Closure;

class Authenticate extends BaseMiddleware
{
    public function handle($request, Closure $next)
    {
        $this->authenticate($request);

        return $next($request);
    }
}

ほぼ親クラスのメソッドを実行しているだけなので親も見てみます。

<?php

namespace Tymon\JWTAuth\Http\Middleware;

use Tymon\JWTAuth\JWTAuth;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Exceptions\JWTException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

abstract class BaseMiddleware
{
    protected $auth;

    public function __construct(JWTAuth $auth)
    {
        $this->auth = $auth;
    }

    public function checkForToken(Request $request)
    {
        if (! $this->auth->parser()->setRequest($request)->hasToken()) {
            throw new UnauthorizedHttpException('jwt-auth', 'Token not provided');
        }
    }

    public function authenticate(Request $request)
    {
        $this->checkForToken($request);

        try {
            if (! $this->auth->parseToken()->authenticate()) {
                throw new UnauthorizedHttpException('jwt-auth', 'User not found');
            }
        } catch (JWTException $e) {
            throw new UnauthorizedHttpException('jwt-auth', $e->getMessage(), $e, $e->getCode());
        }
    }

    protected function setAuthenticationHeader($response, $token = null)
    {
        $token = $token ?: $this->auth->refresh();
        $response->headers->set('Authorization', 'Bearer '.$token);

        return $response;
    }
}

どうやらこのBaseMiddlewareクラスのauthenticateメソッドとcheckForTokenメソッドが
ルーティングにおけるJWTAuthの認証処理の実体のようです。
このことを踏まえて次項に進みます。

 

DBにユーザのロールを扱うカラムを追加

処理実装の前に、ともあれusersテーブルにロールカラムが必要なので追加します。

$ php artisan make:migration add_role_to_users_table
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddRoleToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->integer('role')->default(0);
        });

    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('role');
        });
    }
}
$ php artisan migrate

これでusersテーブルにroleカラムがデフォルト値0で作成されました。
このroleカラムの値で権限を判定します。

 

JWTAuthのクラスを独自クラスで継承・拡張

では、権限での認証処理を実装していきます。
app/Http/Middleware/ ディレクトリに JWTAuthenticateExtended.phpを新規作成し、ひとまずJWTAuthのほうのAuthenticateクラスを継承します。

<?php
namespace App\Http\Middleware;

use Closure;
use Tymon\JWTAuth\Http\Middleware\Authenticate;

class JWTAuthenticateExtended extends Authenticate
{
    public function handle($request, Closure $next)
    {
        $this->authenticate($request);

        return $next($request);
    }
}

とりあえず親と全く同じメソッドを持っているだけの状態です。

そして、こっちのクラスのほうをミドルウェアとして利用したいので、
Kernel.phpに新しいエイリアスを追加しましょう。

    protected $routeMiddleware = [
・・・
        //↓これを追加
        'jwt.auth.extended' => \App\Http\Middleware\JWTAuthenticateExtended::class, 

        'jwt.auth' => \Tymon\JWTAuth\Http\Middleware\Authenticate::class,
        'jwt.refresh' => \Tymon\JWTAuth\Http\Middleware\RefreshToken::class,
・・・

これで「jwt.auth.extended」というミドルウェアをルーティングで利用できるようになりました。
api.phpのほうでこっちのミドルウェアに変更しつつ、
handleメソッドの引数に「どのロールで絞り込むか」を指定できるようにします。

Route::group(['middleware' => ['jwt.auth.extended:admin']], function() {

「jwt.auth.extended」の後ろに「:admin」を付けました。
これでhandleメソッドの第3引数に「admin」という文字列が入ってきますので

public function handle($request, Closure $next, $role=null)

$role変数で受け取ります。

そして、ログイン中のユーザのroleカラムの値に応じて処理を追加したいわけですね。
ユーザオブジェクトは

$this->auth->parseToken()->user()

このメソッドの戻り値で取得できますので、実装は以下のようになります。
※ 管理者ユーザはroleカラムに99が格納されていることとします。

<?php
namespace App\Http\Middleware;

use Closure;
use Tymon\JWTAuth\Http\Middleware\Authenticate;
use Tymon\JWTAuth\Exceptions\JWTException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class JWTAuthenticateExtended extends Authenticate
{
    public function handle($request, Closure $next, $role=null)
    {
        $this->authenticate($request);

        $user = $this->auth->parseToken()->user();
        if ($role == 'admin' && $user['role'] != 99) {
            throw new UnauthorizedHttpException('jwt-auth', 'User role error.');
        }

        return $next($request);
    }
}

これで、roleカラムが99であるユーザのみが認証に通るようになりました。
usersテーブルの値をいじってcurlなどで動作確認してみてください。

ルーティング側の引数とhandleメソッド内の分岐の組み合わせで、自由にrole認証ができるかと思います。
適宜お役立てください。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連する投稿

検索語を上に入力し、 Enter キーを押して検索します。キャンセルするには ESC を押してください。

トップに戻る