Laravel 7.x Sanctumの使い方!実例

こんにちは❗フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、Laravel 7.xの新機能を紹介しはじめてすでに第6回目になりました。今回で一旦Laravel 7.xの新機能につきましては最終回となります。

(実はまだ他にもありますが、小さなものなのでまた別の機会に、と考えています。ぜひご期待ください。m_ _m)

そして、今回紹介する内容ですが、実は1つの機能なのですが使い方が2つあるということで、少し複雑かもしれません。

ただ、使い方によってはとても便利なのでぜひ覚えていただけると嬉しいです。

その機能とは・・・

1.トークンを使ったAPIアクセス
2.SPAの認証機能

で、その名も「Laravel Sanctum」です。

【追記:2020.3.21】元々「Laravel Airlock」という名前でしたが、商標の問題があり変更されました。

ぜひ皆さんのお役に立てると嬉しいです😊✨

開発環境: Laravel 7.x

前提として

通常のログイン機能をインストールし、テストユーザーを準備しておいてください。詳しくは、以下をご覧ください。

Laravel6.0でログイン機能を使う方法

また、今回はアクセスに関連する機能なのでテストに便利なPostmanを使います。詳しくは、Postman をインストールをご覧ください。

パッケージのインストール&準備する

では、まずは以下のコマンドでSanctumをインストールしましょう。

composer require laravel/sanctum

インストールが完了したら、以下のコマンドで必要なファイルをコピーします。

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

これで、

  • /database/migrations
  • /conifg

にファイルが作成されました。
忘れずにマイグレーションも実行しておきましょう。

php artisan migrate

すると、personal_access_tokensテーブルが作成されます。

そして、最後にUserモデルにHasApiTokensをセットしてトークンが使えるようにしたら準備は完了です!

<?php

// 省略
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use Notifiable, HasApiTokens;

    // 省略

}

トークンを使った認証付きのAPI

Sanctumを使って認証付きのAPIを作ってみます。

トークンを発行する

まずは、Sanctumでアクセストークンをつくります。
以下のルートを追加してください。

/routes/web.php

Route::get('/create_token', function(){

    $user = \App\User::find(1);
    $token = $user->createToken('my-api-token');
    echo $token->plainTextToken;    // トークンを表示
    
});

そして、ブラウザでアクセスするとトークンが表示され、personal_access_tokensには以下のようなデータが追加されます。

※表示されているトークンとpersonal_access_tokensに保存されるトークンの中身は違いますが問題ありません。よりセキュアするために暗号化されていると考えてください。

では、ブラウザ上に表示されたアクセス・トークンを使って実際にAPIにアクセスしてみましょう!

一番シンプルな使い方

まずはユーザー情報を取得するだけのシンプルな例を見てみましょう。

/routes/api.php

Route::middleware('auth:sanctum')->group(function(){

    // ここは全て「sanctum」のミドルウェアが適用される

    Route::get('/user', function(Request $request){

        return $request->user();

    });

});

実はたったこれだけで、以下の機能が実装できます。

  1. トークンが正しければユーザー情報を返す
  2. それ以外は拒否(もしくはリダイレクト)する

では、テストとしてPostmanで「/api/user」にアクセスしてみましょう。
まずはトークン「なし」です。

想定通りページがリダイレクトしてアクセス拒否されました。(なお、Ajaxの場合、{"message":"Unauthenticated."}が返ってきます)

では、「Authorization > Bearer Token」に先ほどのトークンを入力してアクセスしてみましょう。

はい!
今回はユーザー情報を取得することができました。

権限もつける

次にトークンの認証に加えて、特定の権限をつけてみましょう。
例えば、「他のユーザー情報を変更できる」権限です。

では、新しいトークンを発行しましょう。

/routes/web.php

Route::get('/create_token', function(){

    $permissions = ['user:update']; // 権限
    $user = \App\User::find(1);
    $token = $user->createToken('my-api-token', $permissions);
    echo $token->plainTextToken;    // トークンを表示

});

ここで重要なのが、createToken()の第2引数に権限を追加している部分です。

そして、personal_access_tokensテーブルのabilitiesには権限が追加されます。

では、tokenCan()を通過できるかを見てみましょう。

/routes/api.php

Route::middleware('auth:sanctum')->group(function(){

    // 省略

    Route::put('/user', function(Request $request) {

        $user = $request->user();

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

            return '権限あり!';

        }

        return '権限なし...';

    });

});

次のようになりました。

はい!
権限があると判断されました。

なお、第2引数を省略した場合abilities["*"]となり、全権限があることになりますので注意してください。

トークンを取得する

ユーザーに与えられたトークンを取得するには、tokensを使います。

$user = $request->user();

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

    $name = $token->name;
    $abilities = $token->abilities;
    dd($name, $abilities);

}

トークンを削除する

トークンの削除は、通常のDBデータ削除と同じです。

Route::delete('/user', function(Request $request){

    $user = $request->user();

    // 全てのトークンを削除
    $user->tokens()->delete();

    // 特定の権限があるトークンだけ削除
    $user->tokens()->whereJsonContains('abilities', 'user:update')->delete();

});

SPAのログイン機能として使う

では、続いてSanctumSPA(※1)のログイン機能として使う場合です。

※1:single-page application(シングルページ・アプリケーション)。簡単に言うとAjaxだけで画面移動をするサイトです。

準備する

まず、SPAのログイン機能を使う場合は、EnsureFrontendRequestsAreStatefulというミドルウェアを有効にする必要があります。

/app/Http/Kernel.php

// 省略

use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
class Kernel extends HttpKernel
{
    // 省略

    protected $middlewareGroups = [

        // 省略

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

    // 省略
}

また、SPAのAjax通信にはaxiosを使うので、npmでビルドしておきましょう。

まず、以下のコマンドでパッケージをインストールします。

npm install

そして、次はビルド(ファイルを1つにしたり省コードにしたりすること)ですが、初期状態では不要なものが含まれているのでコメントアウトしておきます。

/resources/js/app.js

// 省略

// 👇ここ
// Vue.component('example-component', require('./components/ExampleComponent.vue').default);

// 省略

// 👇ここ
// const app = new Vue({
//     el: '#app',
// });

では、この状態でビルドします。
以下のコマンドを実行してください。

npm run dev

※もしくは、本番環境用の場合はこちら。(ただし時間がかかります)

npm run production

これで、/js/app.jsに必要なコードが集まりました。

【注意】なお、実行環境のドメインがlocalhostではない場合は、.envに実際のドメインを追加してSanctumが認識できるようにしておいてください。

SANCTUM_STATEFUL_DOMAINS=l7x.test

SPAページ(ログインフォーム)をつくる

続いて、SPAで使うログインフォームをつくっていきます。

ルートをつくる

/routes/web.php

Route::get('/spa', function(){

    return view('spa');

});

ビューをつくる

/resources/views/spa.blade.php

<html>
<body>
    <div id="app">
        <div v-if="!loggedIn">
            ログインフォーム<br>
            <input type="email" v-model="email">
            <br>
            <input type="password" v-model="password">
            <br>
            <button type="button" @click="login">ログイン</button>
        </div>
        <div v-else>
            ログイン中!<br>
            <button type="button" @click="getUser">ユーザー情報を取得</button>
        </div>
    </div>
    <script src="/js/app.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                loggedIn: false,
                email: '',
                password: ''
            },

            methods: {
                login() {

                    axios.get('/sanctum/csrf-cookie')
                        .then(response => {

                            const url = '/api/login';
                            const params = {
                                email: this.email,
                                password: this.password
                            };
                            axios.post(url, params)
                                .then(response => {

                                    this.loggedIn = response.data.result;

                                })
                                .catch(error => {

                                    alert('ログインに失敗しました。');

                                });

                        });

                },
                getUser() {

                    axios.get('/api/user')
                        .then(response => {

                            console.log(response.data); // ユーザー情報を取得

                        });

                }
            }
        });

    </script>
</body>
</html>

この中で重要なのは、login()です。

このメソッドではまず、Sanctumが用意してくれているURLにアクセスし、さらに/api/loginでログインすることになります。

※なお、この時点でCORSに関連するエラーが表示される場合は、CORSの設定をするをご覧ください。

ブラウザで表示するとこうなります。

ログインする部分をつくる

では、実際にログインをする/api/loginをつくっていきましょう。

/routes/api.php

Route::post('/login', function(Request $request){

    $credentials = $request->validate([
        'email' => 'required|email',
        'password' => 'required'
    ]);

    if(auth()->attempt($credentials)) {

        return ['result' => true];

    }

    return response(['message' => 'ユーザーが見つかりません。'], 422);

});

この中では、送信データをチェックし、その後ログインを試みることになります。

実際にログインが成功するとこうなります。

ユーザー情報を取得する部分をつくる

では、最後にログインした状態でSanctumのミドルウェアで守られたURLにアクセスし、自分のユーザーデータを取得してみましょう。

といっても、取得する先は、トークンの説明の時に作成したapi/userです。

/routes/api.php

Route::middleware('auth:sanctum')->group(function(){

    Route::get('/user', function(Request $request){

        return $request->user();

    });

});

この状態で「ユーザー情報を取得」ボタンをクリックすると以下のようにレスポンスがあります。

テストとして、クッキーを削除してボタンをクリックしてみましょう。

はい!

今度はアクセスが拒否されました。
成功です😊✨

おまけ:CORSの設定をする

実行環境によっては、クロスドメインの制約のせいでアクセスが拒否されることがあります。その場合は次の方法を試してみてください。

まず、axiosにデフォルト設定を追加します。

/resources/js/bootstrap.js

// 省略

window.axios = require('axios');

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.axios.defaults.withCredentials = true; // 👈ここを追加

// 省略

npm run devでビルドします。(もちろんブラウザ側では、ctrl + F5でキャッシュを無視するリロードを忘れないでください)

次に、corsの設定を変更します。
太字が変更した部分です。

/config/cors.php

<?php

return [

    // 省略

    'paths' => ['api/*', 'sanctum/csrf-cookie'], // 👈ここ

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => false,

    'max_age' => false,

    'supports_credentials' => true, // 👈ここ

];

おわりに

・・・ということで、6回に渡ってLaravel 7.xの新機能をご紹介してきましたがいかがだったでしょうか。

正直な気持ちでいうと、すでにLaravel 5.8あたりで「もうこれ以上は便利にならないんじゃないだろうか」と思うほどいたれりつくせりでしたが、まだまだLaravelは進化を続けているようです。

今後の機能にも期待が持てますね。

ではでは〜!

開発のご依頼お待ちしております 😊✨
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly  

開発効率を上げるための機材・まとめ