こんにちは、Czです。
最近急に朝寒くなりましたね。
パーソナルアクセストークン(PAT)とは
Githubとかで使用されているパスワードの代わりに使用するトークンです。
これを使うことでコマンドラインまたはAPIの認証をすることができます。
Laravel Sanctumとは
Sanctumとは「聖域」という意味らしいです。
認証周りを提供してくれているので名前通りかもしれませんね。
Laravel Sanctumは以下の2つの複雑さを解消するために存在しています。
なお、Laravel Sanctum は Laravel8 から標準で入っています。
もし7以前で使いたい場合は、以下のコマンドでインストールすれば行けるはずです。
composer require laravel/sanctum
準備
ではまっさらな状態から作っていきましょう。
Laravel Sail を使って環境を構築します。
curl -s "https://laravel.build/sanctum-sample" | bash cd sanctum-sample ./vendor/bin/sail up
これで環境が立ち上がりました。
http://localhost にアクセスして動いているかどうか確認してみてください。
...
では続いてSanctumを使ってtokenを作っていきたいのですが、tokenを作るためにはログインできなければなりません。
ログイン機能を1から作ると今(午後5時)から始めると日が暮れてしまうので、Laravel Jetstream を使ってサクッと作っていきましょう。
ちなみにJetstreamを入れなくてもSanctumの恩恵は受けることができるので最後まで見て不要だなと思われた方はインストールしなくても大丈夫です。
Laravel Jetstream のインストール
以下のコマンドでJetstreamをインストールすることができます。
./vendor/bin/sail composer require laravel/jetstream ./vendor/bin/sail artisan jetstream:install inertia
install時に指定している「inertia」ですが、他にも「livewire」を指定することができます。
両者の違いとしてlivewireは「blade」、inertiaは「Vue」を使ってフロント側が構築されます。
私の所属するプロジェクトではVueを使っているので今回はinertiaを指定しています。
...
これで認証機能が使えるようになりました。
使ってみよう(Jetstream)
では早速 http://localhost/register へアクセスしてみましょう。
以下のように登録画面が表示されたはずです。
入力して REGISTER ボタンを押して登録してみます。
このようにダッシュボード画面が表示されたはずです。
ダッシュボードの右上のアカウント名をクリックしてみます。
「Profile」と「Log Out」の2つの項目がありますね。
デフォルトではToken機能が有効になっていないので有効化する必要があります。
jetstream.phpを開き、Features::api()
のコメントアウトを外します。
config/jetstream.php 'features' => [ // Features::termsAndPrivacyPolicy(), // Features::profilePhotos(), Features::api(), // Features::teams(['invitations' => true]), Features::accountDeletion(), ],
そうすると、先ほどのメニューに「Api Tokens」が追加されます。
tokenを作ってみよう
メニューからApi Tokensを選択すると次の画面が表示されます。
Nameに「readonly」を入れて、
readにのみチェックを入れて、
CREATE ボタンを押します。
こんな感じでTokenが表示されたはずです。
このTokenは一度限りしか表示されないのでメモしておきましょう。
uScF3qsBW2duh4UoCzyIkkpKwzlEHrDs0mtc0G7B
このtokenを使ってAPI認証することがPATの役割となっています。
tokenを使ってみよう
routes/api.php Route::middleware('auth:sanctum')->get('/user', function (Request $request) { return $request->user(); });
middlewareで「auth:sanctum」ガードが指定されています。 このガードではAuthorizationヘッダのトークンを使ってリクエストの認証を行います。
では、以下のコマンドを実行してみましょう。
// curl -H "Authorization: Bearer <token>" http://localhost/api/user curl -H "Authorization: Bearer uScF3qsBW2duh4UoCzyIkkpKwzlEHrDs0mtc0G7B" http://localhost/api/user {"id":2,"name":"Cz","email":"cz-hogehoge-dummy@google.com","email_verified_at":null,"current_team_id":null,"profile_photo_path":null,"created_at":"2021-10-20T16:14:15.000000Z","updated_at":"2021-10-20T16:14:15.000000Z","profile_photo_url":"https:\/\/ui-avatars.com\/api\/?name=Cz&color=7F9CF5&background=EBF4FF"}
トークンを発行した時のユーザの情報が取れていれば成功です。
...
「ん〜動いたけど、Jetstreamがよしなにやってくれてるからどうやって使うかイメージ湧かない。 」 って方はこの先も読み進めることをお勧めします。
理解しよう
ここからはJetstreamとSanctumの処理を追っていきながら、どう使えばいいか理解を深めていきましょう。
Jetstreamで有効にしたAPI機能の中を見てみる
先ほど config/jetstream.php でFeatures::api()のコメントアウトを外しましたが、
それによってどうなるかを見てみます。
今回はinertiaでインストールしているので以下のファイルを確認します。
vendor/laravel/jetstream/routes/inertia.php // API... if (Jetstream::hasApiFeatures()) { Route::get('/user/api-tokens', [ApiTokenController::class, 'index'])->name('api-tokens.index'); Route::post('/user/api-tokens', [ApiTokenController::class, 'store'])->name('api-tokens.store'); Route::put('/user/api-tokens/{token}', [ApiTokenController::class, 'update'])->name('api-tokens.update'); Route::delete('/user/api-tokens/{token}', [ApiTokenController::class, 'destroy'])->name('api-tokens.destroy'); }
このようにindex
,store
,update
,destroy
の4つの機能が使えるようになるみたいです。
tokenを生成処理はApiTokenController#store()を呼び出しているようなので中を確認してみます。
vendor/laravel/jetstream/src/Http/Controllers/Inertia/ApiTokenController.php /** * Create a new API token. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse */ public function store(Request $request) { $request->validate([ 'name' => ['required', 'string', 'max:255'], ]); $token = $request->user()->createToken( $request->name, Jetstream::validPermissions($request->input('permissions', [])) ); return back()->with('flash', [ 'token' => explode('|', $token->plainTextToken, 2)[1], ]); }
ログインしているユーザのcreateToken()
を使ってトークンの生成が行われていることがわかります。
この機能はUserモデルがtraitで実装している「HasApiToken」で提供されているようです。
HasApiTokenのcreateToken()を見てみる
vendor/laravel/sanctum/src/HasApiTokens.php /** * Create a new personal access token for the user. * * @param string $name * @param array $abilities * @return \Laravel\Sanctum\NewAccessToken */ public function createToken(string $name, array $abilities = ['*']) { $token = $this->tokens()->create([ 'name' => $name, 'token' => hash('sha256', $plainTextToken = Str::random(40)), 'abilities' => $abilities, ]); return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken); }
これを見るとplainTextTokenはStr::random()で生成されており、それをsha256でハッシュ化したものをtokenとして保存しているようですね。
(保存する際にkeyを|
で結合しちゃっているのが気になりますが...)
さらに見ていくと、 戻り値はNewAccessTokenというクラスで、
accessTokenとして保存されるクラスはPersonalAccessTokenだということがわかります。
vendor/laravel/sanctum/src/NewAccessToken.php /** * Create a new access token result. * * @param \Laravel\Sanctum\PersonalAccessToken $accessToken * @param string $plainTextToken * @return void */ public function __construct(PersonalAccessToken $accessToken, string $plainTextToken) { $this->accessToken = $accessToken; $this->plainTextToken = $plainTextToken; }
PersonalAccessTokenを見るとEloquenモデルを継承しているのでDBで管理していることがわかりますね。
use Illuminate\Database\Eloquent\Model; use Laravel\Sanctum\Contracts\HasAbilities; class PersonalAccessToken extends Model implements HasAbilities
対応するpersonal_access_tokenテーブルを確認してみましょう。
personal_access_tokenテーブルを見てみる
Laravel sailではdocker-composeの機能を内包しているので、以下のコマンドでdockerで立ち上がっているmysqlサーバにアクセスすることができます。
./vendor/bin/sail mysql
personal_access_tokenテーブルが存在するsanctum_samleのDBに切り替えます。
mysql> use sanctum_sample; Database changed mysql> show tables; +--------------------------+ | Tables_in_sanctum_sample | +--------------------------+ | failed_jobs | | migrations | | password_resets | | personal_access_tokens | | sessions | | users | +--------------------------+
そして登録されているtoken情報を確認してみます。
mysql> select * from personal_access_tokens\G; *************************** 1. row *************************** id: 1 tokenable_type: App\Models\User tokenable_id: 1 name: readonly token: 2dbe71b931d2f008c9e3555cce089b399d66b99e39acbf31b059bb63f25be3c3 abilities: ["read"] last_used_at: NULL created_at: 2021-10-18 15:18:04 updated_at: 2021-10-18 15:20:17 1 rows in set (0.01 sec)
先ほど作成したtokenがありましたね。 read権限はabilitiesカラムの中におり、json形式で複数設定できるようにしているみたいですね。
保存先が「personal_access_tokens」ということがわかったところで一旦戻ります
plainTextTokenの返却部分を見てみる
再度ApiTokenController#store()を見てみましょう
vendor/laravel/jetstream/src/Http/Controllers/Inertia/ApiTokenController.php /** * Create a new API token. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse */ public function store(Request $request) { $request->validate([ 'name' => ['required', 'string', 'max:255'], ]); $token = $request->user()->createToken( $request->name, Jetstream::validPermissions($request->input('permissions', [])) ); return back()->with('flash', [ 'token' => explode('|', $token->plainTextToken, 2)[1], ]); }
前の画面にリダイレクトする際にflash messageとしてplainTextTokenを返しています。
これでユーザにtokenを渡しているということですね。
なおplainTextTokenはこのように($token->getKey().'|'.$plainTextToken
)keyが結合されているので、わざわざ|
で分割して純粋なtokenのみを抽出しています。
(explodeの第3引数でlimit=2を指定しているところは丁寧だなぁと思いました)
ここまでがSanctumとJetstreamで行っているtoken生成の流れでした。
最小構成で作ってみよう
Jetstreamのコードを追っていって作り方が分かったと思うので最小構成で作ってみましょう。
tokenの作成
superman
というabilityを持つtokenを生成するロジックです。
以下のコードをweb.phpに追記します。(たったこれだけ)
routes/web.php Route::middleware(['auth:sanctum', 'verified'])->get('/token/create/{name}', function (Request $request) { $token = $request->user()->createToken( $request->name, ['superman'] ); return explode('|', $token->plainTextToken, 2)[1]; })
/token/create/<任意のtoken名>
にアクセスするとplainTextTokenが画面に表示されるはずです。
とりあえずこのtokenは控えておきます。
DBにレコードが追加されているか確認してみましょう。
mysql> select * from personal_access_tokens order by id desc limit 1\G; *************************** 1. row *************************** id: 4 tokenable_type: App\Models\User tokenable_id: 1 name: hoge token: 54c8de8c2b2346ece1c4d6257c0c23e40b02681c220eb96c04f2611a590a1bbd abilities: ["superman"] last_used_at: NULL created_at: 2021-10-21 03:38:42 updated_at: 2021-10-21 03:38:42 1 row in set (0.00 sec)
ちゃんと追加されていましたね。
tokenの使用
続いてAPIを作成しましょう。
supermanアビリティを持つユーザでアクセスすると「You are Superman!!」を
持っていない場合は「You are NOT Superman.」を返します。
以下のコードをapi.phpに追記します。(たったこれだけ)
routes/api.php Route::middleware('auth:sanctum')->get('/superman', function (Request $request) { return response()->json([ 'message' => $request->user()->tokenCan('superman') ? 'You are Superman!!' : 'You are NOT Superman.', ]); })
そして実行...
# supermanアビリティを持つtokenでアクセス curl -H "Authorization: Bearer VWWjM8k09fwIayViFaVIBCod0e4GrJIGqoOwCBPy" http://localhost/api/superman {"message":"You are Superman!!"} # supermanアビリティを持たないtokenでアクセス curl -H "Authorization: Bearer uScF3qsBW2duh4UoCzyIkkpKwzlEHrDs0mtc0G7B" http://localhost/api/superman {"message":"You are NOT Superman."} # 正しくないtokenでアクセス curl -H "Authorization: Bearer VWWjM8k09fwIayViFaVIBCod0e4GrJIGqillegal" http://localhost/api/superman <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="refresh" content="0;url='http://localhost/login'" /> <title>Redirecting to http://localhost/login</title> </head> <body> Redirecting to <a href="http://localhost/login">http://localhost/login</a>. </body> </html>
tokenが正しくない時にlogin画面へ行っちゃいましたが、tokenが正しい時はアビリティによって振る舞いを変えることができましたね。
最後に
と、まぁこんな感じで簡単にパーソナルアクセストークンを使うことができました。
パーソナルアークセストークンを使えばブラウザを介さずとも操作できるので、社内ツールの拡張機能として使うのもアリかもですね!
下の方が余ったのでコロナ禍で迎え入れたうちのうーちゃんを紹介します。
(サバトラ白/♀)
ほんと毎日癒されています。
\\『真のユーザーファーストでマーケットを創造する』仲間を募集中です!! //