パスキー

Laravel/passkeysの導入に苦労した

開発元が用意したものを使えばあっさり解決したが、提案のママにAIに書いてもらったコードを使ったがうまくいかず、苦労したので、メモ。フロントエンド側とバックエンド側に開発元が用意したものがあるので、それを使うと簡単に実装できた。公式は以下。

laravel/passkeys - Packagist.org
Passwordless authentication using WebAuthn/passkeys for Lara…
React: 19.2
@laravel/passkeys: 0.2.0
laravel: 12
laravel/passkeys: 0.2.0

フロントエンド

登録と認証と削除が開発元で用意されている模様。以下は登録と認証。削除はなく、自分でapiルートに送って、バックエンド側で開発元の用意した削除機能を使う。

import { Passkeys } from "@laravel/passkeys";

const deviceName = getDeviceName();
await Passkeys.register({
        name: deviceName,
        routes: {
          options: `${BASE}/api/passkeys/options`, // laravelのapiルートに合わせる
          submit: `${BASE}/api/passkeys`,  // laravelのapiルートに合わせる
        },
      });
await Passkeys.verify({
        routes: {
          options: `${BASE}/api/passkeys/login/options`, // laravelのapiルートに合わせる
          submit: `${BASE}/api/passkeys/login`, // laravelのapiルートに合わせる
        },
      })

function getDeviceName() {
  const ua = navigator.userAgent;

  let device = "My Device";

  if (/iPhone/i.test(ua)) device = "iPhone";
  else if (/iPad/i.test(ua)) device = "iPad";
  else if (/Android/i.test(ua)) device = "Android";
  else if (/Macintosh/i.test(ua)) device = "Mac";
  else if (/Windows/i.test(ua)) device = "Windows PC";

  let browser = "Browser";
  if (/Chrome/i.test(ua)) browser = "Chrome";
  else if (/Safari/i.test(ua)) browser = "Safari";
  else if (/Firefox/i.test(ua)) browser = "Firefox";
  else if (/Edg/i.test(ua)) browser = "Edge";

  return `${device} (${browser})`;
}

バックエンド

Passkeyモデルを作ったが、使わなかった。その代わり、Userモデルに追加する必要があるようだ。

use Laravel\Passkeys\Contracts\PasskeyUser;

class User extends Authenticatable implements PasskeyUser {
use PasskeyAuthenticatable;

    public function passkeys(): HasMany
    {
        return $this->hasMany(Passkey::class, 'user_id', 'user_id');
    }

apiルートで、開発元が用意したコントローラーを使えば、うまくいった。一覧取得だけ独自作成のPasskeyControllerを経由した。

use Laravel\Passkeys\Http\Controllers\PasskeyRegistrationController;
use Laravel\Passkeys\Http\Controllers\PasskeyLoginController;

Route::get('/passkeys/login/options', [PasskeyLoginController::class, 'index']);
Route::post('/passkeys/login', [PasskeyController::class, 'store']);

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/passkeys/options', [PasskeyRegistrationController::class, 'index']);
    Route::post('/passkeys', [PasskeyRegistrationController::class, 'store']);
    Route::get('/passkeys', [PasskeyController::class, 'index']);
    Route::delete('/passkeys/{passkey}', [PasskeyRegistrationController::class, 'destroy']);


class PasskeyController extends Controller
{
    public function index(Request $request)
    {
        $user = $request->user();
        $passkeys = $user->passkeys()->orderBy('created_at', 'desc')->get();

        return response()->json($passkeys);
    }

こう見るとスゴイ単純だが、情報が少ないためか、AIが提案する内容に間違いが多い。値を吐かせて、型を作ったりしたが、途中で噛み合わなくなったり、うまくいかなかった。

コメント