Laravel 7.x Laravel Airlock

イントロダクション

Laravel Airlock(エアーロック)はSPA(Single Page Applications)のための、シンプルでトークンベースのAPIを使った羽のように軽い認証システムです。Airlockはアプリケーションのユーザーのアカウントごとに、複数のAPIトークンを生成できます。これらのトークンには、実行可能なアクションを限定するアビリティ・スコープを与えられます。

動作の仕組み

APIトークン

Laravel Airlockは、別々の問題2つを解決するために存在しています。1つ目はOAuthの煩雑さなしにユーザーへAPIトークンを発行するためのシンプルなパッケージの提供です。たとえば、ユーザーがAPIトークンを自分のアカウントへ生成すると想像してください。アプリケーションへ「アカウント設定」ページを用意するでしょう。こうしたトークンを生成し、管理するためにAirlockが使われます。こうしたトークンへは通常数年にも渡る、とても長い有効期間を指定します。しかし、ユーザー自身はいつでも破棄可能です。

この機能を実現するため、Laravelは一つのデータベーステーブルへユーザーのAPIトークンを保存しておき、受信したリクエストがAuthorizationヘッダに有効なAPIトークンを含んでいるかにより認証します。

SPA認証

Tip!! APIトークン認証だけを使う場合、もしくはAPIトークン認証だけを使う場合のどちらにもAirlockは適しています。Airlockが2つの機能を提供しているからと言っても、両方共に使う必要はありません。

2つ目の存在理由は、Laravelが提供するAPIを使用し通信する必要があるシングルページアプリケーション(SPA)へ、シンプルな認証方法を提供するためです。こうしたSPAはLaravelアプリケーションと同じリポジトリにあっても、まったく別のリポジトリに存在していてもかまいません。

Airlockはこの機能の実現のためにトークンは一切使用しません。Laravelへ組み込まれているクッキーベースのセッション認証サービスを使用します。これにより、XSSによる認証情報リークに対する保護と同時に、CSRF保護・セッションの認証を提供しています。皆さんのSPAのフロントエンドから送信されるリクエストに対し、Airlockはクッキーだけを使用して認証を確立しようとします。

インストール

Laravel AirlockはComposerでインストールします。

composer require laravel/airlock

次に、vendor:publish Artisanコマンドを使用して、Airlockの設定とマイグレーションをリソース公開します。airlock設定ファイルがconfigディレクトリに設置されます。

php artisan vendor:publish --provider="Laravel\Airlock\AirlockServiceProvider"

最後に、データベースマイグレーションを実行してください。AirlockはAPIトークンを保存しておくデータベースを1つ作成します。

php artisan migrate

SPAの認証のためにAirlockを活用しようと計画している場合は、app/Http/Kernel.phpファイル中のapiミドルウェアグループへ、Airlockのミドルウェアを追加します。

use Laravel\Airlock\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

APIトークン認証

Tip!! 皆さん自身のファーストパーティSPAを認証するためにAPIトークンを決して利用してはいけません。代わりに、Airlockの組み込みSPA認証を使用してください。

APIトークン発行

APIリクエスト認証に使用するため、APIトークン/パーソナルアクセストークンをAirlockは発行します。APIトークンを利用するリクエストを作成する場合は、BearerトークンとしてAuthorizationヘッダにトークンを含める必要があります。

ユーザーにトークンを発行開始するには、UserモデルでHasApiTokensトレイトを使用してください。

use Laravel\Airlock\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

トークンを発行するには、createTokenメソッドを使用します。このcreateTokenメソッドはLaravel\Airlock\NewAccessTokenインスタンスを返します。APIトークンはデータベースへ格納される前に、SHA-256を使いハッシュされますが、NewAccessTokenインスタンスのplainTextTokenプロパティにより、平文の値へアクセスできます。トークンを生成したら、ユーザーへこの値を表示しなくてはなりません。

$token = $user->createToken('token-name');

return $token->plainTextToken;

そのユーザーのトークンすべてにアクセスするには、HasApiTokensトレイトが提供するtokens Eloquentリレーションを使用します。

foreach ($user->tokens as $token) {
    //
}

トークンのアビリティ

OAuthの「スコープ」と同じように、Airlockは「アビリティ(能力)」をトークンへ割り付けられます。createTokenメソッドの第2引数として、アビリティの文字列の配列を渡してください。

return $user->createToken('token-name', ['server:update'])->plainTextToken;

Airlockにより認証されたリクエストを処理するとき、そのトークンが特定のアビリティを持っているかをtokenCanメソッドで判定できます。

if ($user->tokenCan('server:update')) {
    //
}

Tip!! 利便性のため、tokenCanメソッドは受信認証済みリクエストがファーストパーティSPAから送信されたとき、もしくはAirlockの組み込みSPA認証を使用している場合は常にtrueを返します。

ルート保護

受信リクエストをすべて認証済みに限定し、ルートを保護する場合は、routes/api.phpファイル中のAPIルートに対してairlock認証ガードを指定する必要があります。このガードは受信リクエストが認証済みであると保証します。そのリクエストが皆さん自身のSPAからのステートフルな認証済みであるか、もしくはサードパーティからのリクエストの場合は有効なAPIトークンのヘッダを持っているかのどちらか一方であるか確認します。

Route::middleware('auth:airlock')->get('/user', function (Request $request) {
    return $request->user();
});

トークン破棄

データベースから削除し、トークンを「破棄」するには、HasApiTokensトレイトが提供しているtokensリレーションを使用します。

// 全トークンの破棄
$user->tokens()->delete();

// 特定トークンの破棄
$user->tokens()->where('id', $id)->delete();

SPA認証

Laravelが提供するAPIを使用し通信する必要があるシングルページアプリケーション(SPA)へ、シンプルな認証方法を提供するためです。こうしたSPAはLaravelアプリケーションと同じリポジトリにあっても、もしくはVue CLIを使用して生成したSPAのように、まったく別のリポジトリに存在していてもかまいません。

Airlockはこの機能の実現のためにトークンは一切使用しません。Laravelへ組み込まれているクッキーベースのセッション認証サービスを使用します。これにより、XSSによる認証情報リークに対する保護と同時に、CSRF保護・セッションの認証を提供しています。皆さんのSPAのフロントエンドから送信されるリクエストに対し、Airlockはクッキーだけを使用して認証を確立しようとします。

設定

ファーストパーティドメインの設定

最初に、どのドメインから皆さんのSPAがリクエストを作成するのか設定する必要があります。airlock設定ファイルのstateful設定オプションを利用してこのドメインを指定します。この設定を元にして皆さんのAPIへリクエストを作成するときに、Laravelのセッションクッキーを使用することで「ステートフル」な認証を維持する必要があるドメインを判断します。

Airlockミドルウェア

次に、app/Http/Kernel.phpファイル中のapiミドルウェアグループへ、Airlockのミドルウェアを追加する必要があります。このミドルウェアは皆さんのSPAから受信するリクエストが、Laravelのセッションクッキーを使用して確実に認証できるようにする責任を負っています。同時に、サードパーティやモバイルアプリからのリクエストに対し、APIトークンを使用した認証ができるようにしています。

use Laravel\Airlock\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

CORSとクッキー

別のサブドメイン上のSPAからの認証でアプリケーションにトラブルが起きているのであれば、CORS(Cross-Origin Resource Sharing)かセッションクッキーの設定を間違えたのでしょう。

アプリケーションのCORS設定で、Access-Control-Allow-CredentialsヘッダにTrueの値を返すため、アプリケーションのcors設定ファイル中のsupports_credentialsオプションをtrueにしてください。

さらに、グローバルaxiosインスタンス上で、withCredentialsオプションを有効にしているかも確認してください。通常、これはresources/js/bootstrap.jsファイルで実行されるべきです。

axios.defaults.withCredentials = true;

最後に、アプリケーションのセッションクッキードメイン設定で、ルートドメイン下の全サブドメインをサポートしているかを確認する必要があります。session設定ファイル中で、.にドメイン名を続ければ指定できます。

'domain' => '.domain.com',

認証

皆さんのSPAを認証するには、SPAのログインページで最初に/airlock/csrf-cookieルートへのリクエストを作成し、アプリケーションのCSRF保護を初期化しなくてはなりません。

axios.get('/airlock/csrf-cookie').then(response => {
    // ログイン処理
});

CSRF保護の初期化後、通常Laravelでは/loginであるルートへPOSTリクエストを送る必要があります。このloginルートは、laravel/ui 認証スカフォールドが提供しています。

ログインリクエストに成功するとユーザーは認証され、Laravelのバックエンドがクライアントへ発行しているセッションクッキーにより、APIルートに対する以降のリクエストも自動的に認証されます。

Tip!! /loginエンドポイントは自由に書けます。ただし標準的な、Laravelが提供する認証サービスベースのセッションをユーザー認証で確実に使用してください。

ルート保護

受信リクエストをすべて認証済みに限定し、ルートを保護する場合は、routes/api.phpファイル中のAPIルートに対してairlock認証ガードを指定する必要があります。このガードは受信リクエストが認証済みであると保証します。そのリクエストが皆さん自身のSPAからのステートフルな認証済みであるか、もしくはサードパーティからのリクエストの場合は有効なAPIトークンのヘッダを持っているかのどちらか一方であるか確認します。

Route::middleware('auth:airlock')->get('/user', function (Request $request) {
    return $request->user();
});

プライベートブロードキャストチャンネルの認証

SPAでプライベート/プレゼンスブロードキャストチャンネルを使った認証が必要な場合は、routes/api.phpファイルの中でBroadcast::routesメソッドを呼び出す必要があります。

Broadcast::routes(['middleware' => ['auth:airlock']]);

次に、Pusherの認証リクエストを成功させるため、Laravel Echoの初期化時に、カスタムPusher authorizerを用意する必要があります。これにより、アプリケーションが確実にクロスドメインのリクエストを処理できるように設定したaxiosインスタンスをPusherが使用するように設定できます。

window.Echo = new Echo({
    broadcaster: "pusher",
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    encrypted: true,
    key: process.env.MIX_PUSHER_APP_KEY,
    authorizer: (channel, options) => {
        return {
            authorize: (socketId, callback) => {
                axios.post('/api/broadcasting/auth', {
                    socket_id: socketId,
                    channel_name: channel.name
                })
                .then(response => {
                    callback(false, response.data);
                })
                .catch(error => {
                    callback(true, error);
                });
            }
        };
    },
})

モバイルアプリの認証

あなた自身のAPIに対するモバイルアプリのリクエストを認証するために、Airlockトークンを使用できます。モバイルアプリのリクエストに対する認証手順は、サードパーティAPIリクエストに対する認証と似ています。しかし、APIトークンの発行方法に多少の違いがあります。

APIトークン発行

まずはじめに、ユーザーのメールアドレス/ユーザー名、パスワード、デバイス名を受け取るルートを作成し、次にこうした認証情報を元に新しいAirlockトークンを受け取ります。このエンドポイントは平文のAirlockトークンを返し、それはモバイルデバイス上に保存され、それ以降のAPIリクエストを作成するために利用されます。

use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

Route::post('/airlock/token', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
        'device_name' => 'required'
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    return $user->createToken($request->device_name)->plainTextToken;
});

モバイルデバイスが、アプリケーションへのAPIリクエストを作成するためにトークンを使用するときは、BearerトークンとしてAuthorizationヘッダへそのトークンを渡します。

Tip!! モバイルアプリケーションに対してトークンを発行するときにも、自由にトークンのアビリティを指定できます。

ルート保護

以前に説明した通り、ルートへairlock認証ガードを指定することで、受信リクエストをすべて認証済み必須にし、ルートを保護できます。通常、routes/api.phpファイルでこのガードを指定したルートを定義します。

Route::middleware('auth:airlock')->get('/user', function (Request $request) {
    return $request->user();
});

トークン破棄

モバイルデバイスに対して発行されたAPIトークンをユーザーが破棄できるようにするため、WebアプリケーションのUIで「アカウント設定」のようなページに一覧表示し、「破棄」ボタンを用意する必要があります。ユーザーが「破棄」ボタンをクリックしたら、データベースからトークンを削除します。HasApiTokensトレイトが提供するtokensリレーションにより、そのユーザーのAPIトークンへアクセスできるのを覚えておきましょう。

// 全トークンの破棄
$user->tokens()->delete();

// 特定トークンの破棄
$user->tokens()->where('id', $id)->delete();

テスト

テストをする時は、Airlock::actingAsメソッドでユーザーを認証し、トークンにアビリティを許可する指定を行えます。

use App\User;
use Laravel\Airlock\Airlock;

public function test_task_list_can_be_retrieved()
{
    Airlock::actingAs(
        factory(User::class)->create(),
        ['view-tasks']
    );

    $response = $this->get('/api/task');

    $response->assertOk();
}

トークンに全アビリティを許可したい場合は、actingAsメソッドへ*を含めたアビリティリストを指定します。

Airlock::actingAs(
    factory(User::class)->create(),
    ['*']
);

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる