完全なユーザー認証とアクセス制御– Laravel Passportチュートリアル、Pt。 1

公開: 2022-03-11

Webアプリケーションを開発するときは、通常、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認証を追加するために、同じファイルにloginlogout 、および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); }

この後、 loginregister 、および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/userGETリクエストを送信しようとすると、次のメッセージが表示されます。

 { "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に、 nameemail (一意である必要があります)、 passwordpassword_confirmationのパラメーターを使用して送信します。

Postmanを使用して/api/registerにPOSTリクエストを送信するスクリーンショット。

ユーザーが作成されると、APIはトークンを返します。トークンは、認証の手段として以降のリクエストで使用されます。

ログインするには、 POSTリクエストを/api/loginに送信します。 クレデンシャルが正しければ、この方法でLaravelログインAPIからトークンも取得します。

Postmanを使用して/api/loginにPOSTリクエストを送信するスクリーンショット。

このリクエストから返される認証トークンは、保護されたルートにアクセスするときに使用できます。 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 [ // ]; } }

この新しい通知を利用するには、 UserAuthenticatableクラスから継承する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.phphandle関数も編集する必要があります。

 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.adminapi.superAdminなどになります。

ミドルウェアを作成するために必要なのはこれだけです。

すべてを一緒に入れて

認証とアクセス制御が機能していることをテストするために、いくつかの追加の手順を実行する必要があります。

Laravel認証とアクセス制御のテスト:ステップ1

ArticleControllerindex関数を変更してルートを登録する必要があります。 (実際のプロジェクトでは、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

これで、認証トークンなしでルートへのアクセスを試みることができます。 認証エラーが発生するはずです。

Postmanを使用して/api/ articlesにGETリクエストを送信し、応答として「Unauthenticated」というメッセージを受信したスクリーンショット。

Laravel認証とアクセス制御のテスト:ステップ3

また、認証トークン(この記事の前半で登録またはログインして取得したもの)を使用して同じルートにアクセスすることもできます。

場合によっては、次のようなエラーが発生する可能性があります。

 Unknown column 'api_token' in 'where clause' (SQL: select * from `users` where `api_token` = ... 

Postmanを使用してapi/articlesルートを取得した場合の不明な列エラーのスクリーンショット。

これが発生した場合、開発者は必ずPassport移行を実行し、 config/auth.php['guards']['api']['driver']passportに設定する必要があります。

 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],

その後、構成キャッシュも更新する必要があります。

それが修正されると、ルートにアクセスできるようになります。

通常のJSON応答を使用して、Postmanを使用して/ api/articlesにGETリクエストを送信するスクリーンショット。

Laravel認証とアクセス制御のテスト:ステップ4

アクセス制御をテストする時が来ました。 記事ルートに->middleware('api.admin')を追加して、次のようにします。

 Route::get('/articles', 'ArticleController@index')->middleware('api.admin')->name('articles');

api/userルートで確認できるように、新しく作成されたユーザーにタイプ0が自動的に割り当てられるようにしました。

Postmanを使用して/api/userにGETリクエストを送信するスクリーンショット。応答には、ID、名前、電子メール、null電子メール検証タイムスタンプ、記入済みの作成および更新されたタイムスタンプ、およびタイプが含まれます。

そのため、そのようなユーザーとしてarticlesエンドポイントにアクセスしようとするとエラーが発生するはずです。

Postmanを使用して/api/articlesにGETリクエストを送信するスクリーンショットとPermissionDeniedJSONレスポンス。

テストの目的で、データベース内のユーザーを1のtypeに変更してみましょうapi/userルートを介してその変更を再度確認した後、 /articles/ルートのGETを再試行する準備が整いました。

Postmanを使用してLaravel認証ユーザーとして/api/ articlesにGETリクエストを送信するスクリーンショット。これは、以前のリクエストと同じですが、このリクエストの応答時間が890ミリ秒ではなく1,280ミリ秒であった点が異なります。

それは完璧に動作します。

より複雑なアプリケーションを作成している開発者は、適切なアクセス制御がこれほど単純ではないことに注意する必要があります。 その場合、他のサードパーティアプリケーションまたはLaravelのゲートとポリシーを使用して、カスタムユーザーアクセス制御を実装できます。 このシリーズの第2部では、より堅牢で柔軟なアクセス制御ソリューションについて説明します。

Laravel API認証:私たちが学んだこと

このLaravelPassportチュートリアルでは、次のことについて説明しました。

  1. Laravel Passportの例をテストするときに使用するものを用意するために、ダミーのコントローラーとモデルを作成します。
  2. APIをスムーズに実行するために必要なミドルウェアを作成し、CORSに対処し、APIに常にJSON応答を返すように強制します。
  3. 基本的なLaravelAPI認証の設定:登録、ログイン、およびログアウト。
  4. Laravelのデフォルトに基づいて「パスワードリセット」機能を設定します。
  5. アクセス制御ミドルウェアを作成して、さまざまなルートにユーザー認証許可レベルを追加します。

これらは、Laravel開発サービスの分野で働くすべての人にとって不可欠なスキルです。 読者はこのGitHubリポジトリで最終結果を見つけることができ、Laravelで認証を実装するための適切な位置に配置されているはずです。 以下のコメントをお待ちしております。