Laravel Passportを使用したPHPによるセキュアなAPIの構築

April 20, 2020
執筆者
Oluyemi Olususi
寄稿者
Twilio の寄稿者によって表明された意見は彼ら自身のものです
レビュー担当者

Laravel Passportを使用したPHPによるセキュアなAPIの構築

この記事はOluyemi Olususiこちらで公開した記事(英語)を日本語化したものです。

モバイルアプリケーションのバックエンドを構築する場合や、最新のJavaScriptフレームワークを使用する場合は、RESTful APIを避けて通ることはできません。APIは、2つのソフトウェアプログラムが相互に通信するためのインターフェイスです。APIを使用した通信では、リクエスト間でセッション状態が保持されない点に注意が必要です。そのため、ユーザーを認証し、認可するためにトークンを使用する必要があります。

Laravelにはセキュリティを適切に確保できる仕組みがあらかじめ用意されているため、このようなリソースを簡単に構築できます。このチュートリアルでは、Laravel Passportを使用してセキュアなLaravelバックエンドAPIを構築する方法について説明します。最後まで進めると、セキュアなLaravel APIを作成する方法や、既存のAPIのセキュリティを強化する方法を習得できます。

必要条件

このチュートリアルを進めるにあたり、Laravelを使用したアプリケーション構築の基礎知識があると役立ちます。また、依存関係を管理するために、Composerがグローバルインストールされていること、エンドポイントのテストにPostmanがそれぞれ必要です。

構築する内容

大手テクノロジー企業のCEOのリストを作成するAPIを構築し、セキュアなLaravel APIの構築方法を学びます。このアプリケーションは、各CEOについて以下の情報を一覧表示します。

  • 名前
  • CEOに就任した年
  • 企業の本社所在地
  • 企業の業務内容

このアプリケーションのセキュリティを確保するために、Laravel Passportをインストールし、認証された各ユーザーに対してアクセストークンを生成します。アクセストークンを受信したユーザーは、セキュリティ保護されたエンドポイントにアクセスできます。

はじめに

まず、ComposerまたはLaravelインストーラーを使用し、コンピューターに新しいLaravelアプリケーションのための骨組みを作成します。Laravelの公式Webサイトの手順に従い、Laravelインストーラーをセットアップします。セットアップが終了したら、以下のコマンドを実行します。

$ laravel new laravel-backend-api

Composerを使用して同じアプリケーションをインストールする場合は、以下のコマンドを実行します。

$ composer create-project --prefer-dist laravel/laravel laravel-backend-api


このコマンドを実行すると、指定したパラメーターの内容に従い、Laravelとその依存関係をインストールした開発フォルダー内にlaravel-backend-apiという新しいフォルダーが作成されます。

この新しいフォルダーに移動し、以下のようにLaravel Artisanコマンドを使用してアプリケーションを実行します。

// move into the project
$ cd laravel-backend-api

// run the application
$ php artisan serve


ブラウザからhttp://localhost:8000にアクセスすると、以下のようなページが表示されます。

Laravel ホームページ

データベースの作成と接続

Laravelがインストールされ、実行できました。次に、データベースへの接続を確立します。まず、データベースが作成済みであることを確認し、.envファイル内の以下の変数の値を更新します。

  • DB_DATABASE
  • DB_USERNAME
  • DB_PASSWORD

これでデータベースの設定は完了ですが、APIを構築する前にLaravel Passportのインストールと設定が必要です。

Laravel Passportのインストールと設定

Laravel Passportは、Laravelアプリケーション向けに0Auth2サーバーが必要とするあらゆる機能を実装しています。Laravel Passportにより、簡単にパーソナルアクセストークンを生成し、現在認証されているユーザーを一意に識別できます。生成したトークンをすべてのリクエストに追加することにより、各ユーザーは保護されたルートにアクセスできます。最初に、Ctrl+Cキーを押し、アプリケーションの実行を停止します。次に、Composerを使用し、Laravel Passportをインストールします。以下のコマンドを実行します。

 

$ composer require laravel/passport

インストールを実行すると、クライアントとアクセストークンの情報を格納するために必要なテーブルを含む新しいマイグレーションファイルが生成されます。このファイルをアプリケーションで利用します。以下のコマンドを実行し、データベースをマイグレーションします。

$ php artisan migrate

次に、以下のコマンドを実行し、セキュアなアクセストークンを生成するために必要な暗号化キーを作成します。

$ php artisan passport:install

上記のコマンドによるインストール処理が完了した後、以下のようにApp\UserモデルにLaravel\Passport\HasApiTokensトレイトを追加します。

// app/User.php

<?php

namespace App;

...
use Laravel\Passport\HasApiTokens; // include this

class User extends Authenticatable
{
    use Notifiable, HasApiTokens; // update this line

    ...
}

このトレイトを追加すると、認証済みユーザーのトークンとスコープを検査するためのヘルパーメソッドをモデルで使用できるようになります。

個人アクセストークンとクライアントアクセストークンの発行と取り消しに必要なルートを登録するために、Passport::routesメソッドをAuthServiceProviderbootメソッド内で呼び出します。app/Providers/AuthServiceProviderファイルを開き、以下のように内容を更新します。

// app/Providers/AuthServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport; // add this 

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
         'App\Model' => 'App\Policies\ModelPolicy', // uncomment this line
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Passport::routes(); // Add this
    }
}

Passport::routes()を登録すると、Laravel Passportを使用してアプリケーション内のすべての認証と認可のプロセスを処理する準備がほぼ整いました。

最後に、アプリケーションがPassportのTokenGuardを使用してあらゆる受信APIリクエストを認証できるようにします。config/auth設定ファイルを開き、以下のようにapi認証ガードのdriverオプションをpassportに設定します。

// config/auth

<?php

return [
    ...

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

        'api' => [
            'driver' => 'passport', // set this to passport
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    ...
];

 

企業情報のマイグレーションファイルの作成

Laravelを新たにインストールすると、Userモデルとマイグレーションファイルが自動的に生成されます。これはデータベースの標準的な構造を保持するのに便利です。app/User.phpファイルを開き、以下のような内容であることを確認します。

// app/User.php

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use Notifiable, HasApiTokens;

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

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

database/migrations/***_create_users_table.phpのユーザーマイグレーションファイルについても以下の内容であることを確認します。

<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

ここで作成するアプリケーションのユーザーに求める認証情報は、上記のファイルに指定されたフィールドで十分です。そのため、変更の必要はありません。

次に、artisanコマンドを使用してモデルのインスタンスを作成し、CEOテーブルのデータベースマイグレーションファイルを生成します。以下のコマンドを実行します。

$ php artisan make:model CEO -m


上記のコマンドを実行すると、appディレクトリ内にモデルが作成されるとともに、database/migrationsフォルダーに新しいマイグレーションファイルが作成されます。-mオプションは--migrationを略したものです。artisanコマンドに対してモデルのマイグレーションファイルを作成するよう指示します。

次に、新たに作成したマイグレーションファイルを開き、以下のように内容を更新します。

<?php

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

class CreateCEOSTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('c_e_o_s', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('company_name');
            $table->year('year');
            $table->string('company_headquarters');
            $table->string('what_company_does');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('c_e_o_s');
    }
}

ここでは、namecompany_nameyearcompany_headquarterswhat_company_doesの各フィールドを追加しました。

app/CEO.phpファイルを開き、以下のように内容を更新します。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class CEO extends Model
{
    protected $fillable = [
        'name', 'company_name', 'year', 'company_headquarters', 'what_company_does'
    ];
}

デフォルトで、Eloquentモデルは一括割り当てが行われないよう保護されています。そのため、ここでは一括割り当てする属性を指定しています。

再度、以下のマイグレーションコマンドを実行し、新たに作成したテーブルとフィールドを使用してデータベースを更新します。

$ php artisan migrate

データベースが更新された後、アプリケーションのコントローラーを作成します。さらに、登録、ログイン、CEOの詳細情報の作成などを行うエンドポイントも作成します。

コントローラーの作成

コントローラーは受信HTTPリクエストを受け付け、適切なアクションまたはメソッドにリダイレクトすることにより、リクエストを処理し、適切なレスポンスを返します。ここではAPIを構築しているため、ほとんどのレスポンスはJSON形式となります。JSONは、RESTful APIで標準的に使用される形式です。

認証コントローラー

まず、artisanコマンドを使用し、アプリケーションの認証コントローラーを生成します。このコントローラーは、アプリケーションへのユーザーの登録とログインのリクエストを処理します。以下のコマンドを実行します。

$ php artisan make:controller API/AuthController

このコマンドを実行すると、app/Http/Controllers内に新たにAPIフォルダーが作成され、そのフォルダー内にAuthController.phpという新しいファイルが作成されます。新たに作成されたコントローラーファイルを開き、以下のように内容を更新します。

<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;

class AuthController extends Controller
{
    public function register(Request $request)
    {
        $validatedData = $request->validate([
            'name' => 'required|max:55',
            'email' => 'email|required|unique:users',
            'password' => 'required|confirmed'
        ]);

        $validatedData['password'] = bcrypt($request->password);

        $user = User::create($validatedData);

        $accessToken = $user->createToken('authToken')->accessToken;

        return response([ 'user' => $user, 'access_token' => $accessToken]);
    }

    public function login(Request $request)
    {
        $loginData = $request->validate([
            'email' => 'email|required',
            'password' => 'required'
        ]);

        if (!auth()->attempt($loginData)) {
            return response(['message' => 'Invalid Credentials']);
        }

        $accessToken = auth()->user()->createToken('authToken')->accessToken;

        return response(['user' => auth()->user(), 'access_token' => $accessToken]);

    }
}

 

上記のregisterメソッドにより、アプリケーションのユーザーに対する登録プロセスが処理されます。登録に必要なすべてのフィールドが入力されているかを検証するために、Laravelの認証メソッドを使用しています。このバリデーターにより、nameemailpasswordpassword_confirmationの各必須フィールドに値が指定されているかが確認され、適切なフィードバックが返されます。

最後に、loginメソッドにより、適切な認証情報が入力されているかが確認され、ユーザーの認証が行われます。正常に認証されると、ログインしたユーザーを一意に識別するaccessTokenが生成され、JSONレスポンスが送信されます。保護されたセキュアなルートに後続のHTTPリクエストを送信して正常に処理を行うには、生成されたaccessTokenを認証ヘッダーとして渡す必要があります。このトークンを渡さない場合、認証されていない事を示すレスポンスが返されます。

CEOコントローラーの作成

同じartisanコマンドを使用し、新しいコントローラーを自動的に作成します。今回は、APIリソースコントローラーを作成します。Laravelのリソースコントローラーとは、特定のモデルに対するすべてのHTTPリクエストを処理するコントローラーです。この場合は、CEOモデルに対するすべての作成、読み取り、更新、削除リクエストを処理するコントローラーを作成します。以下のコマンドを実行します。

$ php artisan make:controller API/CEOController --api --model=CEO

上記のコマンドを実行すると、APIリソースコントローラーが生成されます。ここではAPIを構築するだけであるため、createとeditビューは含まれていません。app/Http/Controllers/API/CEOController.phpに移動し、以下のように内容を更新します。

<?php

namespace App\Http\Controllers\API;

use App\CEO;
use App\Http\Controllers\Controller;
use App\Http\Resources\CEOResource;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class CEOController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $ceos = CEO::all();
        return response([ 'ceos' => CEOResource::collection($ceos), 'message' => 'Retrieved successfully'], 200);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $data = $request->all();

        $validator = Validator::make($data, [
            'name' => 'required|max:255',
            'year' => 'required|max:255',
            'company_headquarters' => 'required|max:255',
            'what_company_does' => 'required'
        ]);

        if($validator->fails()){
            return response(['error' => $validator->errors(), 'Validation Error']);
        }

        $ceo = CEO::create($data);

        return response([ 'ceo' => new CEOResource($ceo), 'message' => 'Created successfully'], 200);
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\CEO  $ceo
     * @return \Illuminate\Http\Response
     */
    public function show(CEO $ceo)
    {
        return response([ 'ceo' => new CEOResource($ceo), 'message' => 'Retrieved successfully'], 200);

    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\CEO  $ceo
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, CEO $ceo)
    {

        $ceo->update($request->all());

        return response([ 'ceo' => new CEOResource($ceo), 'message' => 'Retrieved successfully'], 200);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param \App\CEO $ceo
     * @return \Illuminate\Http\Response
     * @throws \Exception
     */
    public function destroy(CEO $ceo)
    {
        $ceo->delete();

        return response(['message' => 'Deleted']);
    }
}

上記のコードスニペットでは5つの異なるメソッドが作成されており、それぞれのメソッドには特定の機能を実行するためのロジックが組み込まれています。各メソッドの機能の概要は以下のとおりです。

  • index: このメソッドは、データベースからすべてのCEOのリストを取得し、JSON構造のリソースコレクションとして返します。Laravelのリソースコレクションの詳細については、次のセクションで説明します。
  • store: このメソッドは、HTTPリクエストのインスタンスを受け取り、依存性の注入により新しいCEOの詳細情報を作成します。送信されたすべての値は$requestを使用して取得します。また、リクエストのすべての必須フィールドに値が指定されているか確認します。最後に、新たに作成されたCEOの詳細情報をJSONレスポンスとして返します。
  • show: このメソッドは、特定のCEOを一意に識別し、その詳細情報をJSONレスポンスとして返します。
  • update: このメソッドは、HTTPリクエストと、編集する必要がある特定のアイテムをパラメーターとして受け取ります。渡されたモデルのインスタンスに対して更新を実行し、適切なレスポンスを返します。
  • destroy: このメソッドは、削除する必要がある特定のアイテムのインスタンスを受け取り、データベースから削除します。

show()update()destroy()などの一部のメソッドでは、特定のアイテムを一意に識別するための具体的なモデルIDが必要になります。ここでは、モデルのインスタンスを直接注入できる点に注目してください。これは、Laravelの暗黙的ルートモデルバインディングを使用して実現されています。

この方法を使用すると、メソッドにCEOのインスタンスが自動的に注入されます。見つからない場合はステータスコード404が返されます。これにより、クエリを実行してそのIDに対応するモデルを取得することなく、モデルのインスタンスを直接使用できるため、処理が簡単になります。

リソースの作成

Laravel Eloquentリソースを使用すると、モデルやコレクションをJSON形式に変換できます。リソースは、データベースとコントローラーの間のデータ変換レイヤーとしての役割を果たします。これにより、アプリケーション内のあらゆる場所で使用できる、統一されたインターフェイスを提供できます。以下のコマンドを使用し、CEOモデルのリソースを作成します。

$ php artisan make:resource CEOResource

このコマンドを実行すると、app/Http/Resourcesディレクトリ内にCEOResource.phpという新しいリソースファイルが作成されます。新しく作成されたファイルを開きます。内容は以下のとおりです。

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class CEOResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return parent::toArray($request);
    }
}

toArray()メソッド内のparent::toArray($request)は、JSONレスポンスを送信する際に、公開されているすべてのモデル属性を自動的に変換します。

Eloquentリソースを使用するメリットについて詳しくは、Laravel公式ドキュメントの「Eloquent: API Resources」をご覧ください。

ルートファイルの更新

コントローラー内に作成されたメソッドのエンドポイント設定を完了するには、以下のようにroutes.api.phpファイルの内容を更新します。

Route::post('/register', 'Api\AuthController@register');
Route::post('/login', 'Api\AuthController@login');

Route::apiResource('/ceo', 'Api\CEOController')->middleware('auth:api');

このアプリケーションで作成しているすべてのルートのリストを表示するには、ターミナルで以下のコマンドを実行します。

$ php artisan route:list

以下のような内容が表示されます。

アウトプット

これにより、アプリケーションのすべてのルートを確認できます。

routes/api.phpファイル内のすべてのルートにはプレフィックス/api/が付加されるため、先ほど作成したエンドポイントにHTTPリクエストを送信する場合は、このプレフィックスを追加する必要があります。

アプリケーションの実行

次に、以下のコマンドでアプリケーションを実行し、ここまでに実装したすべてのロジックをテストします。

$ php artisan serve

このチュートリアルでは、これ以降、Postmanを使用してエンドポイントをテストします。まだマシンにインストールしていない場合は、Postman公式サイトからダウンロードしてください。

ユーザーの登録

ユーザーを登録するには、エンドポイントhttp://localhost:8000/api/registerにPOST HTTPリクエストを送信し、以下のように適切な詳細情報を入力します。

詳細の入力

具体的な詳細は異なる可能性がありますが、以下のようにリクエストのキーと値を指定します。

KEY                                       VALUE
name                                     John Doe
email                                    john@me.com
password                                  password
password_confirmation          password

ログイン

正常に登録が完了した後、http://localhost:8000/api/loginにアクセスして詳細情報を入力し、認証を受けることができます。

ログイン

前のセクションで指定したキーと値を使用した場合、リクエストは以下のようになります。

KEY                                       VALUE
email                                    john@me.com
password                                  password

ベアラートークンの追加

ログイン後、レスポンスのaccess_tokenの値をコピーし、[Authorization]タブをクリックします。ドロップダウンから[Bearer Token]を選択し、先ほどコピーしたaccess_tokenの値を貼り付けます。

ベアラートークン追加

CEOの作成

以下に示すような詳細情報を指定し、新しいCEOを作成します。

CEO作成

CEOのリストの取得

ここまでに作成したCEOのリストを取得するには、以下に示すようにエンドポイントhttp://localhost:8000/api/ceoに対してGETHTTPリクエストを送信します。

CEOリスト取得

CEOの表示

URL http://localhost:8000/api/ceo/1にGET HTTPリクエストを送信し、特定のCEOの詳細情報を表示します。

CEO表示

ここで、エンドポイントとしてデータベースから取得する特定のレコードのidを示す1が使用されていることに注意してください。idを使用して任意のレコードを対象に情報を表示できます。

CEOの編集

CEOリスト編集
CEO削除

まとめ

このチュートリアルでは、Laravel Passportを使用し、Laravelによる安全なRESTful APIを構築する方法について学習しました。このチュートリアルで作成した例は、多くのアプリケーションで必要となる基本的なCRUD(作成、読み取り、更新、削除)のプロセスをカバーしています。

ここで学んだ内容を基礎とし、既存のプロジェクトや新規のプロジェクトで応用していただければ幸いです。このアプリケーションのコードベースはGitHubで公開されており、自由にダウンロードして参照できます。

LaravelとLaravel Passportの詳細については、公式ドキュメントをご参照ください。

Olususi Oluyemi氏はテクノロジー、プログラミング、Web開発の熱心な愛好家で、最先端のテクノロジーに強い関心を持っています。