完全なユーザー認証とアクセス制御– Laravel Passportチュートリアル、Pt。 1
公開: 2022-03-11Webアプリケーションを開発するときは、通常、2つの層に分割することをお勧めします。 中間層APIはデータベースと対話し、Web層は通常フロントエンドSPAまたはMPAで構成されます。 このように、Webアプリケーションはより緩く結合され、長期的には管理とデバッグが容易になります。
APIが作成されている場合、ステートレスAPIコンテキストで認証と状態を設定することはやや問題があるように思われるかもしれません。
この記事では、LaravelとPassportを使用してAPIに完全なユーザー認証と単純な形式のアクセス制御を実装する方法を見ていきます。 これは入門チュートリアルではないため、Laravelの使用経験が必要です。
インストールの前提条件:
- PHP 7以降、MySQL、およびApache(3つすべてを一度にインストールしたい開発者はXAMPPを使用できます)。
- 作曲
- Laravel 7
- Laravelパスポート。 APIは通常ステートレスであり、セッションを使用しないため、通常、トークンを使用してリクエスト間の状態を維持します。 Laravelは、Passportライブラリを使用して、APIでの認証に使用できる完全なOAuth2サーバーを実装します。
- APIをテストするためのPostman、cURL、またはInsomnia-これは個人の好み次第です
- お好みのテキストエディタ
- Laravelヘルパー(Laravel 6.0以降の場合)-LaravelとPassportをインストールした後、次のコマンドを実行します。
composer require laravel/helpers
上記をインストールしたら、開始する準備が整いました。 .env
ファイルを編集して、データベース接続を設定してください。
Laravel Passportチュートリアル、ステップ1:ダミーリクエスト用のコントローラーとモデルを追加する
まず、ダミーリクエスト用のコントローラーとモデルを作成します。 このチュートリアルでは、このモデルはあまり役に立ちません。コントローラーが操作するデータの概念を示すためだけのものです。
モデルとコントローラーを作成する前に、移行を作成する必要があります。 ターミナル(またはWindowsを使用している場合はcmd.exe
ウィンドウ)で、次のコマンドを実行します。
php artisan make:migration create_articles_table --create=articles
次に、 database/migrations
フォルダーに移動し、 xxxx_xx_xx_xxxxxx_create_articles_table.php
のような名前のファイルを開きます。
クラスのup
関数では、次のように記述します。
Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->string('body'); $table->integer('user_id'); $table->timestamps(); });
次に、 Article
モデルを作成します。 これを行うには、次を実行します。
php artisan make:model Article
次に、以下を実行してArticleController
コントローラーを作成します。
php artisan make:controller ArticleController --resource
次に、ファイルapp/Providers/AppServiceProvider.php
を編集し、次を追加してIlluminate\Support\Facades\Schema
クラスをインポートします。
use Illuminate\Support\Facades\Schema
…ファイルの上部にあるインポートの下部に。
次に、 boot
関数で次のように記述します。
Schema::defaultStringLength(191);
これがすべて完了したら、次を実行できます。
php artisan migrate
…上記で作成した移行を適用します。
Laravel Passportチュートリアル、ステップ2:必要なミドルウェアを作成する
ここでは、APIが機能するために必要なミドルウェアを追加します。
JSON応答
最初に必要なのはForceJsonResponse
ミドルウェアで、これはすべての応答を自動的にJSONに変換します。
これを行うには、次を実行します。
php artisan make:middleware ForceJsonResponse
そして、これはApp/Http/Middleware/ForceJsonReponse.php
にあるそのミドルウェアのハンドル関数です。
public function handle($request, Closure $next) { $request->headers->set('Accept', 'application/json'); return $next($request); }
次に、 $routeMiddleware
配列のapp/Http/Kernel.php
ファイルにミドルウェアを追加します。
'json.response' => \App\Http\Middleware\ForceJsonResponse::class,
次に、同じファイルの$middleware
配列にも追加します。
\App\Http\Middleware\ForceJsonResponse::class,
これにより、 ForceJsonResponse
ミドルウェアがすべてのリクエストで実行されるようになります。
CORS(クロスオリジンリソースシェアリング)
Laravel REST APIの利用者が別のオリジンからアクセスできるようにするには、CORSを設定する必要があります。 そのために、 Cors
と呼ばれるミドルウェアを作成します。
ターミナルまたはコマンドプロンプトで、プロジェクトのルートディレクトリにcd
して、次のコマンドを実行します。
php artisan make:middleware Cors
次に、 app/Http/Middleware/Cors.php
に次のコードを追加します。
public function handle($request, Closure $next) { return $next($request) ->header('Access-Control-Allow-Origin', '*') ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') ->header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, X-Token-Auth, Authorization'); }
このミドルウェアをロードするには、 app/Http/Kernel.php
の$routeMiddleware
配列に行を追加する必要があります。
'cors' => \App\Http\Middleware\Cors::class,
また、以前のミドルウェアで行ったように、それを$middleware
配列に追加する必要があります。
\App\Http\Middleware\Cors::class,
その後、このルートグループをroutes/api.php
に追加します。
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... });
以下に示すように、すべてのAPIルートがその関数に入ります。
Laravel Passportチュートリアル、ステップ3:APIのユーザー認証コントローラーを作成する
次に、 login
機能とregister
機能を備えた認証コントローラーを作成します。
まず、以下を実行します。
php artisan make:controller Auth/ApiAuthController
次に、いくつかのクラスをファイルapp/Http/Controllers/Auth/ApiAuthController.php
にインポートします。 これらのクラスは、 login
関数とregister
関数の作成に使用されます。 次を追加してクラスをインポートします。
use App\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str;
…コントローラーの上部に。
ここで、ユーザーにLaravel API認証を追加するために、同じファイルにlogin
、 logout
、およびregister
(サインアップ)関数を作成します。
register
関数は次のようになります。
public function register (Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6|confirmed', ]); if ($validator->fails()) { return response(['errors'=>$validator->errors()->all()], 422); } $request['password']=Hash::make($request['password']); $request['remember_token'] = Str::random(10); $user = User::create($request->toArray()); $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); }
login
機能は次のようになります。
public function login (Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|string|email|max:255', 'password' => 'required|string|min:6|confirmed', ]); if ($validator->fails()) { return response(['errors'=>$validator->errors()->all()], 422); } $user = User::where('email', $request->email)->first(); if ($user) { if (Hash::check($request->password, $user->password)) { $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); } else { $response = ["message" => "Password mismatch"]; return response($response, 422); } } else { $response = ["message" =>'User does not exist']; return response($response, 422); } }
そして最後に、 logout
機能:
public function logout (Request $request) { $token = $request->user()->token(); $token->revoke(); $response = ['message' => 'You have been successfully logged out!']; return response($response, 200); }
この後、 login
、 register
、およびlogout
機能をルートに追加する必要があります。つまり、すでにAPIにあるルートグループ内に追加する必要があります。
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... // public routes Route::post('/login', 'Auth\ApiAuthController@login')->name('login.api'); Route::post('/register','Auth\ApiAuthController@register')->name('register.api'); Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); // ... });
最後に、 HasApiToken
トレイトをUser
モデルに追加する必要があります。 app/User
に移動し、次のことを確認します。
use HasApiTokens, Notifiable;
…クラスのトップに。
これまでのところ…
アプリケーションサーバーを起動して(つまり、 php artisan serve
を実行して)、ルート/api/user
にGET
リクエストを送信しようとすると、次のメッセージが表示されます。
{ "message": "Unauthenticated." }
これは、そのルートにアクセスするための認証が行われていないためです。 選択したルートの一部を保護するために、 Route::post
行の直後にroutes/api.php
に追加できます。
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here });
先に進む前に、ログアウトルートをauth:api
ミドルウェアに追加します。これは、Laravelがトークンを使用してユーザーをログアウトするためです。このトークンは、 auth:api
ミドルウェアの外部からはアクセスできません。 私たちの公共ルートは次のようになります。
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... // public routes Route::post('/login', 'Auth\ApiAuthController@login')->name('login.api'); Route::post('/register', 'Auth\ApiAuthController@register')->name('register.api'); // ... });
一方、保護されたルートは次のようになります。
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); });
次に、 app/Http/Controllers/ArticleController.php
で作成したArticleController
に移動し、そのクラスのcreate
メソッドとedit
メソッドを削除します。 その後、残りの各関数に、少し編集した次のコードを追加します。
$response = ['message' => '<function name> function']; return response($response, 200);
必要に応じて<function name>
を入力します。 たとえば、 update
関数の本体は次のようになります。
$response = ['message' => 'update function']; return response($response, 200);
手動Laravel認証テスト:ユーザーの作成
ユーザーを登録するには、 POST
リクエストを/api/register
に、 name
、 email
(一意である必要があります)、 password
、 password_confirmation
のパラメーターを使用して送信します。
ユーザーが作成されると、APIはトークンを返します。トークンは、認証の手段として以降のリクエストで使用されます。
ログインするには、 POST
リクエストを/api/login
に送信します。 クレデンシャルが正しければ、この方法でLaravelログインAPIからトークンも取得します。
このリクエストから返される認証トークンは、保護されたルートにアクセスするときに使用できます。 Postmanでは、[認証]タブにドロップダウンがあり、タイプを[ベアラートークン]に設定できます。その後、トークンをトークンフィールドに入力できます。
プロセスは不眠症でも非常に似ています。
cURLユーザーは、パラメーター-H "Authorization: Bearer <token>"
を渡すことで同等のことを行うことができます。ここで、 <token>
は、ログインまたはレジスターの応答から与えられる認証トークンです。
cURLと同様に、開発者がaxiosまたはその種のライブラリを使用してAPIを使用することを計画している場合は、値Bearer <token>
を使用してAuthorization
ヘッダーを追加できます。
Laravel Passportチュートリアル、ステップ4:パスワードリセット機能の作成
基本認証が完了したので、パスワードリセット機能を設定します。
これを行うには、 api_auth
コントローラーディレクトリを作成し、新しいカスタムコントローラーを作成し、関数を実装することを選択できます。 または、Laravelで生成できる認証コントローラーを編集することもできます。 この場合、アプリケーション全体がAPIであるため、認証コントローラーを編集します。

まず、以下を実行して認証コントローラーを生成します。
composer require laravel/ui php artisan ui vue --auth
app/Http/Controllers/Auth/ForgotPasswordController.php
でクラスを編集し、次の2つのメソッドを追加します。
protected function sendResetLinkResponse(Request $request, $response) { $response = ['message' => "Password reset email sent"]; return response($response, 200); } protected function sendResetLinkFailedResponse(Request $request, $response) { $response = "Email could not be sent to this email address"; return response($response, 500); }
次に、実際にパスワードをリセットするコントローラーを設定する必要があるため、 app/Http/Controllers/Auth/ResetPasswordController.php
に移動して、次のようなデフォルトの関数をオーバーライドします。
protected function resetPassword($user, $password) { $user->password = Hash::make($password); $user->save(); event(new PasswordReset($user)); } protected function sendResetResponse(Request $request, $response) { $response = ['message' => "Password reset successful"]; return response($response, 200); } protected function sendResetFailedResponse(Request $request, $response) { $response = "Token Invalid"; return response($response, 401); }
また、次を追加して、コントローラーにいくつかのクラスをインポートする必要があります。
use Illuminate\Auth\Events\PasswordReset; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash;
…コントローラーの上部に。
Laravelに付属するメール通知は承認にAPIトークンを使用しないため、使用するメール通知も変更する必要があります。 次のコマンドを実行して、 app/Notifications
の下に新しいものを作成できます。
php artisan make:notification MailResetPasswordNotification
app/Notifications/MailResetPasswordNotification.php
ファイルを次のように編集する必要があります。
<?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Auth\Notifications\ResetPassword; use Illuminate\Support\Facades\Lang; class MailResetPasswordNotification extends ResetPassword { use Queueable; protected $pageUrl; public $token; /** * Create a new notification instance. * * @param $token */ public function __construct($token) { parent::__construct($token); $this->pageUrl = 'localhost:8080'; // we can set whatever we want here, or use .env to set environmental variables } /** * Get the notification's delivery channels. * * @param mixed $notifiable * @return array */ public function via($notifiable) { return ['mail']; } /** * Get the mail representation of the notification. * * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) { if (static::$toMailCallback) { return call_user_func(static::$toMailCallback, $notifiable, $this->token); } return (new MailMessage) ->subject(Lang::getFromJson('Reset application Password')) ->line(Lang::getFromJson('You are receiving this email because we received a password reset request for your account.')) ->action(Lang::getFromJson('Reset Password'), $this->pageUrl."?token=".$this->token) ->line(Lang::getFromJson('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.users.expire')])) ->line(Lang::getFromJson('If you did not request a password reset, no further action is required.')); } /** * Get the array representation of the notification. * * @param mixed $notifiable * @return array */ public function toArray($notifiable) { return [ // ]; } }
この新しい通知を利用するには、 User
がAuthenticatable
クラスから継承するsendPasswordResetNotification
メソッドをオーバーライドする必要があります。 これをapp/User.php
に追加するだけです:
public function sendPasswordResetNotification($token) { $this->notify(new \App\Notifications\MailResetPasswordNotification($token)); }
メールの設定が適切に機能していれば、この時点で通知が機能しているはずです。
現在残っているのは、ユーザーアクセス制御だけです。
Laravel Passportチュートリアル、ステップ5:アクセス制御ミドルウェアの作成
アクセス制御ミドルウェアを作成する前に、 user
テーブルを更新してtype
という名前の列を作成する必要があります。この列は、ユーザーレベルを決定するために使用されます。タイプ0は通常のユーザー、タイプ1は管理者、タイプ2はスーパー管理者。
user
テーブルを更新するには、次のコマンドを実行して移行を作成する必要があります。
php artisan make:migration update_users_table_to_include_type --table=users
新しく作成されたdatabase/migrations/[timestamp]_update_users_table.php
の形式のファイルで、 up
関数とdown
関数を更新して、それぞれtype
列を追加および削除する必要があります。
public function up() { Schema::table('users', function (Blueprint $table) { $table->integer('type'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropIfExists('type'); }); }
次に、 php artisan migrate
を実行します。 これが完了したら、 ApiAuthController.php
ファイルのregister
関数を編集して、 $user = User::create($request->toArray());
の行の直前にこれを追加する必要があります。 :
$request['type'] = $request['type'] ? $request['type'] : 0;
また、次の行を$validator
配列に追加する必要があります。
'type' => 'integer',
これら2つの編集の最初の編集では、すべての登録ユーザーがデフォルトで「通常のユーザー」になります。つまり、ユーザータイプが入力されていない場合です。
アクセス制御ミドルウェア自体
これで、アクセス制御に使用する2つのミドルウェアを作成できるようになりました。1つは管理者用、もう1つはスーパー管理者用です。
実行します:
php artisan make:middleware AdminAuth php artisan make:middleware SuperAdminAuth
まず、 app/Http/Middleware/AdminAuth.php
に移動し、 Illuminate\Support\Facades\Auth
をインポートしてから、次のようにhandle
関数を編集します。
public function handle($request, Closure $next) { if (Auth::guard('api')->check() && $request->user()->type >= 1) { return $next($request); } else { $message = ["message" => "Permission Denied"]; return response($message, 401); } }
app/Http/Middleware/SuperAdminAuth.php
のhandle
関数も編集する必要があります。
public function handle($request, Closure $next) { if (Auth::guard('api')->check() && $request->user()->type >= 2) { return $next($request); } else { $message = ["message" => "Permission Denied"]; return response($message, 401); } }
また、次を追加して、両方のファイルの先頭にあるAuth
クラスをインポートする必要があります。
use Illuminate\Support\Facades\Auth;
…そこにある輸入品の一番下まで。
新しいミドルウェアを使用するために、 $routeMiddleware
配列に次の行を追加して、カーネル内(つまり、 app/Http/Kernel.php
)の両方のクラスを参照します。
'api.admin' => \App\Http\Middleware\AdminAuth::class, 'api.superAdmin' => \App\Http\Middleware\SuperAdminAuth::class,
開発者が特定のルートでミドルウェアを使用したい場合は、次のようにミドルウェアをルート関数に追加するだけです。
Route::post('route','Controller@method')->middleware('<middleware-name-here>');
この場合<middleware-name-here>
は、必要に応じてapi.admin
、 api.superAdmin
などになります。
ミドルウェアを作成するために必要なのはこれだけです。
すべてを一緒に入れて
認証とアクセス制御が機能していることをテストするために、いくつかの追加の手順を実行する必要があります。
Laravel認証とアクセス制御のテスト:ステップ1
ArticleController
のindex
関数を変更してルートを登録する必要があります。 (実際のプロジェクトでは、PHPUnitを使用し、自動テストの一部としてこれを実行します。ここでは、テスト目的でルートを手動で追加しています。後で削除できます。)
app/Http/Controllers/ArticleController
にあるArticleController
コントローラーに移動し、 index
関数を次のように変更します。
public function index() { $response = ['message' => 'article index']; return response($response, 200); }
次に、 routes/api.php
ファイルに移動し、これを追加して、関数をルートに登録します。
Route::middleware('auth:api')->group(function () { Route::get('/articles', 'ArticleController@index')->name('articles'); });
Laravel認証とアクセス制御のテスト:ステップ2
これで、認証トークンなしでルートへのアクセスを試みることができます。 認証エラーが発生するはずです。
Laravel認証とアクセス制御のテスト:ステップ3
また、認証トークン(この記事の前半で登録またはログインして取得したもの)を使用して同じルートにアクセスすることもできます。
場合によっては、次のようなエラーが発生する可能性があります。
Unknown column 'api_token' in 'where clause' (SQL: select * from `users` where `api_token` = ...
これが発生した場合、開発者は必ずPassport移行を実行し、 config/auth.php
で['guards']['api']['driver']
をpassport
に設定する必要があります。
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
その後、構成キャッシュも更新する必要があります。
それが修正されると、ルートにアクセスできるようになります。
Laravel認証とアクセス制御のテスト:ステップ4
アクセス制御をテストする時が来ました。 記事ルートに->middleware('api.admin')
を追加して、次のようにします。
Route::get('/articles', 'ArticleController@index')->middleware('api.admin')->name('articles');
api/user
ルートで確認できるように、新しく作成されたユーザーにタイプ0が自動的に割り当てられるようにしました。
そのため、そのようなユーザーとしてarticles
エンドポイントにアクセスしようとするとエラーが発生するはずです。
テストの目的で、データベース内のユーザーを1のtype
に変更してみましょうapi/user
ルートを介してその変更を再度確認した後、 /articles/
ルートのGET
を再試行する準備が整いました。
それは完璧に動作します。
より複雑なアプリケーションを作成している開発者は、適切なアクセス制御がこれほど単純ではないことに注意する必要があります。 その場合、他のサードパーティアプリケーションまたはLaravelのゲートとポリシーを使用して、カスタムユーザーアクセス制御を実装できます。 このシリーズの第2部では、より堅牢で柔軟なアクセス制御ソリューションについて説明します。
Laravel API認証:私たちが学んだこと
このLaravelPassportチュートリアルでは、次のことについて説明しました。
- Laravel Passportの例をテストするときに使用するものを用意するために、ダミーのコントローラーとモデルを作成します。
- APIをスムーズに実行するために必要なミドルウェアを作成し、CORSに対処し、APIに常にJSON応答を返すように強制します。
- 基本的なLaravelAPI認証の設定:登録、ログイン、およびログアウト。
- Laravelのデフォルトに基づいて「パスワードリセット」機能を設定します。
- アクセス制御ミドルウェアを作成して、さまざまなルートにユーザー認証許可レベルを追加します。
これらは、Laravel開発サービスの分野で働くすべての人にとって不可欠なスキルです。 読者はこのGitHubリポジトリで最終結果を見つけることができ、Laravelで認証を実装するための適切な位置に配置されているはずです。 以下のコメントをお待ちしております。