Laravel

Laravel Fortifyとは|画面を持たない認証バックエンドの責務と2FA・パスキーの実装

AIを活用したセキュリティソリューションのサービス内容

Laravelで認証を組むとき、実装者が最初に決めるのは「画面ごと雛形をもらうか、処理だけをもらうか」です。Laravel Fortifyは後者を担い、ログイン・ユーザー登録・パスワードリセット・メール確認・2要素認証・パスキーのルートとコントローラだけを登録します。画面は一切持ちません。スターターキットの内側で動いているのもこのFortifyです。この記事では、fortify:installの生成物、features配列で変わる公開範囲、2要素認証とパスキーの実装順を、2026年8月29日時点の1.39.0のソースと13.xの公式ドキュメントで確認しました。

まとめ:Fortifyが担う範囲と採否を分ける判断軸

先に結論を置きます。版に依存する記述は1.39.0のソースと13.x公式ドキュメントで確認しました。

  • Fortifyはフロントエンド非依存の認証バックエンドです。ルートとコントローラを登録するだけで、ログイン画面もユーザー登録画面も開発側が用意します
  • 公開されるルートは設定ファイルのfeatures配列が決めます。指定できるのは7種類で、要らない機能は配列から外せばルートごと消えます
  • スターターキットから始めた案件では認証を既にFortifyが担当しています。追加インストールが要るのは自前のUIへ後から足す場合です
  • SanctumとFortifyは競合しません。Sanctumはトークンとセッションの認証、Fortifyは登録やリセットといった入口の処理を持ちます
  • 2要素認証はTOTPのみで、有効化・QRコード表示・確認の3ステップを踏みます。確認を省くと中途半端な状態で残ります
  • 1.37.0でパスキー(WebAuthn)が加わりました。パスワードレスが要件なら、TOTPと並ぶ選択肢がここです

Laravel Fortifyとは:画面を持たない認証バックエンドの責務

Fortifyの位置づけは、生成されるものと生成されないものの線から押さえます。曖昧なまま入れると、インストール直後に「ログイン画面が出ない」で止まります。

Fortifyはルートとコントローラを登録し、画面は開発側が用意する

公式ドキュメントはFortifyを「フロントエンド非依存の認証バックエンド実装」と定義しています。インストール後にroute:listを叩くと、ログインやユーザー登録のルートが既に登録されています。ただし、そこへ返すビューをFortifyは持ちません。ログイン画面のHTMLは開発側が書き、そのフォームがFortifyの登録したエンドポイントへPOSTする、という分担です。

おかげでBladeでもInertiaでも素のJavaScriptでも同じバックエンドが使えます。裏返すと、画面込みの雛形が欲しい案件では単体で足りません。その判断の前提はLaravel Breeze非推奨の理由とスターターキット移行ガイドで整理しています。

features配列が公開するルートを決める仕組みと7つの機能

features配列は単なる機能フラグではありません。ここに入っている項目だけがルートとして公開されます。指定できるのは次の7種類です。

機能 features配列の指定 画面の要否
ユーザー登録 registration 要る
パスワードリセット resetPasswords 要る(2画面)
メール確認 emailVerification 要る
プロフィール更新 updateProfileInformation 不要
パスワード更新 updatePasswords 不要
2要素認証 twoFactorAuthentication 要る(設定と入力)
パスキー passkeys 要る(登録と一覧)

publishされる設定には7つが並び、メール確認だけコメントアウト済みです。公式ドキュメントは、多くのアプリで登録・パスワードリセット・メール確認の3つに絞るよう勧めています。要件に無い機能を残すと、画面の無いエンドポイントが外から叩ける状態で残ります。削る側で判断してください。

'features' => [
    Features::registration(),
    Features::resetPasswords(),
    Features::emailVerification(),
    Features::twoFactorAuthentication([
        'confirm' => true,
        'confirmPassword' => true,
    ]),
],

Sanctumとの違いは責務の分担にあり、併用も想定されている

FortifyとSanctumはよく混同されますが、解いている問題が違います。Sanctumが扱うのはAPIトークンの発行と管理、そしてセッションクッキーやトークンによる既存ユーザーの認証で、登録やリセットのルートは持ちません。Fortifyはその反対に、入口の処理を持ちながらトークン管理へは関与しません。

そのため、SPAやモバイルアプリのバックエンドを自前で組む案件では、両方を入れる構成が公式ドキュメントでも想定されています。Fortifyが登録とリセットを引き受け、Sanctumがセッションとトークンを見る並べ方です。ガードは既定のwebのまま組み合わせます。

導入手順:fortify:installが生成するファイルと初期設定

インストールは2コマンドですが、生成物が4種類へ分かれます。どれを触るのかを先に押さえると迷いません。

composer requireからmigrateまでの導入コマンドの流れ

まずcomposerでlaravel/fortifyを入れ、続いてphp artisan fortify:installを実行します。このコマンドはアクションクラスをapp配下のActionsディレクトリへ、FortifyServiceProviderをProviders配下へ、設定ファイルをconfigへ、マイグレーションをdatabaseへ配置。最後にphp artisan migrateでテーブルを更新します。

1.39.0が要求するのはPHP 8.2以上で、Laravelは11・12・13系に対応します。PHP 8.1とLaravel 10のサポートは1.37.0で終了しました。既存案件へ入れるときは、この下限を先に確認してください。版の調べ方はLaravelのバージョン確認|artisanとcomposerの違いとサポート期限にまとめています。

viewsをfalseにする判断とSPA構成で残すべきルート名

設定ファイルのviewsは、ビューを返すルートを登録するかどうかの切り替えです。JavaScript側で画面を組むSPAではビュー用ルートが不要なため、falseにして丸ごと止められます。LaravelとReactの連携・使い方ガイドのようなSPA構成では、この設定を落としてJSONだけを受け取ります。

ただし例外が1つ。パスワードリセットを実装するなら、viewsをfalseにしてもpassword.resetという名前付きルートは自前で定義してください。リセット通知がこの名前付きルートからURLを組み立てるためです。落とすと、メールは飛ぶのにリンク生成で例外になります。

設定ファイルのfortify.phpで先に決めておく値の要点

publishされる設定の既定値のうち、着手時に確認したいのは4つです。guardはweb、usernameはemail、ログイン後の遷移先homeはホーム直下、lowercase_usernamesはtrue。ログインIDをメール以外にする要件ではusernameを変え、フォームの入力名も揃えます。

ルートのパスを既存のURL設計へ寄せたい場合は、prefixとdomain、さらにpaths配下で個別に差し替えられます。既にログインURLが決まっている改修案件では、フロント側の変更を抑えられます。なお、ログインフォームは通常のPOSTですから、419が出るならCSRFトークンの側を疑ってください。切り分けはInvalid CSRF tokenの意味と直し方|Laravel 419・Django対応で扱っています。

認証処理の差し替え:認証パイプラインとレスポンスを要件別に調整する方法

Fortifyは3か所を押さえて差し替えられるようにしておくと、要件の揺れに耐えます。資格情報の判定、ログイン処理のパイプライン、そして応答です。

まずビューの返し方をFortifyクラスのメソッドで登録する

画面を返す指示は、FortifyServiceProviderのbootメソッドから登録する仕組みです。loginViewやtwoFactorChallengeViewへクロージャを渡すと、Fortifyが対応するルートを定義してそのビューを返します。書き忘れると、ルートは存在するのに画面が出ない状態になります。

use Laravel\Fortify\Fortify;

public function boot(): void
{
    Fortify::loginView(fn () => view('auth.login'));
    Fortify::registerView(fn () => view('auth.register'));
    Fortify::twoFactorChallengeView(fn () => view('auth.two-factor-challenge'));
}

authenticateUsingで資格情報の判定ロジックを差し替える

既定のFortifyは、設定されたガードに従って資格情報を検証します。停止フラグを持つユーザーを弾く、社員番号とパスワードで照合する、といった案件固有の判定が要るときはauthenticateUsingにクロージャを渡してください。クロージャはリクエストを受け取り、認証できたユーザーを返し、できなければnullかfalseを返します。ここで条件を足せば、退会済みや承認待ちのユーザーをログインさせない制御を1か所へ集約できます。入力値そのものの検証ルールの置き場はLaravelのバリデーション|validateとFormRequestの使い分けの考え方へ揃える方針です。

authenticateThroughでパイプラインを組み替えて制限を変える

Fortifyはログイン要求を、invokableなクラスの連なりへ通して処理します。既定の並びはスロットリング、ユーザー名の正規化、2要素認証への振り分け、認証試行、セッション準備の5段です。authenticateThroughへ配列を返すクロージャを渡すと、この並びを差し替えられます。

Fortify::authenticateThrough(function (Request $request) {
    return array_filter([
        config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class,
        config('fortify.lowercase_usernames') ? CanonicalizeUsername::class : null,
        Features::enabled(Features::twoFactorAuthentication()) ? RedirectIfTwoFactorAuthenticatable::class : null,
        AttemptToAuthenticate::class,
        PrepareAuthenticatedSession::class,
    ]);
});

既定のEnsureLoginIsNotThrottledは、ユーザー名とIPアドレスの組み合わせで試行を絞ります。IPアドレスだけで絞る、社内からの試行は緩める、といった要件ではlimitersのlogin項目へ独自のレートリミッタ名を指定してください。publishされる設定にはlogin・two-factor・passkeysの3系統があり、別々のリミッタを当てられます。

遷移先とユーザー作成処理は契約とアクションクラスで案件別に変更する

ログイン後やログアウト後の遷移を細かく制御したいときは、LoginResponseやLogoutResponseの契約実装をサービスコンテナへバインドし、権限に応じて遷移先を分ける処理をここへ収めます。ユーザー作成そのものを変えたいなら、生成されたCreateNewUserアクションを編集してください。パスワードリセットはResetUserPasswordが担当です。内部を継承で追う必要はなく、生成済みのアクションを書き換えるだけで済みます。

TOTPによる2要素認証の実装:Fortifyで有効化から確認まで進める手順

2要素認証はFortifyで最も手順が多い機能です。叩く順序が決まっており、途中で止めるとユーザーが中途半端な状態で残ります。

TwoFactorAuthenticatableトレイトと設定画面の下準備

まずUserモデルにTwoFactorAuthenticatableトレイトを追加し、ユーザーが2要素認証を有効化・無効化し、リカバリコードを再生成できる設定画面を作ります。この画面もFortifyは用意しません。

ここで前提が1つ。publishされる設定では、2要素認証の変更にパスワード確認を求めるオプションが既定で有効です。パスワード確認の機能を先に実装していないと設定操作が通らないため、順序はパスワード確認が先になります。会員側のマイページではなく管理画面へ設定を置くなら、Laravel Filamentとは?管理画面を短工数で作る構成のような管理画面フレームワークへ載せる選択もあります。

有効化のPOSTとQRコード表示、確認までを進める3つの実装ステップ

有効化は2要素認証のエンドポイントへのPOSTで始まります。成功するとセッションのstatusにtwo-factor-authentication-enabledが入りますが、この時点では確定していません。ユーザーがQRコードを読み取り、生成された6桁を確認用エンドポイントへPOSTして初めて確定し、statusはtwo-factor-authentication-confirmedへ変わります。

QRコードは、Bladeならユーザーインスタンスのメソッドから直接SVGを取得できます。JavaScript側で描くならQRコードのエンドポイントへXHRでGETすると、svgキーを持つJSONが返ります。リカバリコードも同じく、Bladeからはメソッド、フロントからは専用エンドポイントへのGETで取れ、再生成は同じパスへのPOSTです。なお1.39.0では、有効化を繰り返したときに確定前の秘密鍵が消える不具合が修正されました。有効化ボタンを二度押しできる画面なら、この版まで上げておくと安全側に倒せます。

ログイン時に表示するチャレンジ画面とXHR応答の分岐を実装する方法

2要素認証を有効にしたユーザーがログインすると、Fortifyはチャレンジ画面へ振り分けます。通常のフォーム送信ならリダイレクトで済みますが、XHRでログインした場合はJSON応答にtwo_factorという真偽値が入るため、フロント側でこれを見て遷移させます。

チャレンジ画面のフォームは専用エンドポイントへPOSTし、認証アプリの6桁を入れるcodeか、リカバリコードを入れるrecovery_codeのどちらかを送ります。成功時、XHRなら204。失敗時は422で検証エラーが返るため、コード誤りの表示はこの分岐で組みます。無効化は有効化と同じパスへのDELETEで、これもパスワード確認済みが前提です。

パスキー:1.37系で加わったパスワードレス認証を契約設定から実装する手順

2026年4月の1.37.0でパスキー(WebAuthn)が入りました。Face IDやWindows Hello、ハードウェアキーによる認証で、TOTPとは別系統の選択肢です。日本語の解説が少ないため設定の要点を押さえます。

Fortifyの有効化に必要な契約とトレイトおよび設定値を実装前に確認する

features配列へpasskeysを追加し、UserモデルへPasskeyUser契約の実装とPasskeyAuthenticatableトレイトを与えます。confirmPasswordオプションを立てると、登録と削除の前にパスワード確認を求めます。

class User extends Authenticatable implements PasskeyUser
{
    use Notifiable, TwoFactorAuthenticatable, PasskeyAuthenticatable;
}

設定は同じファイルのpasskeys配列です。relying_party_idはアプリのURLから取り出したホスト名、allowed_originsは登録と認証を通すブラウザのオリジン、timeoutは60000ミリ秒が既定。Fortifyはパスキー用パッケージを包んで設定するため、そちらの設定ファイルのpublishは不要です。値はFortify側が上書きします。

'passkeys' => [
    'relying_party_id' => parse_url(config('app.url'), PHP_URL_HOST),
    'allowed_origins' => [config('app.url')],
    'timeout' => 60000,
],

登録と認証のエンドポイントおよび公式JSクライアントを使う手順

認証は、まずログインオプションのエンドポイントへGETしてWebAuthnのチャレンジを受け取り、ブラウザの資格情報取得APIへ渡します。返った資格情報をログイン用エンドポイントへPOSTすれば成立です。登録は登録オプションへGETし、資格情報作成APIで作った値を名前付きでPOST、削除はパスキーのIDを付けたDELETEになります。

ブラウザ側の手順は、npmで配布される公式クライアントを入れるとregisterとverifyの2メソッドへ包まれます。React・Vue・Svelte向けのヘルパも同梱され、自前でWebAuthnのシリアライズを書くより事故が少ない選択です。

本番環境で詰まりやすいドメインとオリジンの不一致を確認する方法

パスキーで詰まる原因の大半は設定値の不一致です。relying_party_idはアプリのドメインと一致している必要があり、アプリURLの設定がローカルのまま本番へ出ると認証が通りません。allowed_originsも、独自ドメインとサブドメインを併用する構成では両方を並べます。ステージングを別ドメインで持つ案件では、環境ごとに値が変わる点を構築手順へ書き残してください。もう1つ、user_handle_secretは不透明なユーザー識別子を導出する値で、既定はアプリケーションキーです。これが変わると同一ユーザーと認識されなくなるため、キーのローテーションを運用へ組み込むなら環境変数で固定します。

受託開発でFortifyを採る条件と、見送ってよい場面の線引き

ここからは判断の話です。Fortifyは「入れておけば安全」な部品ではなく、案件の入り方によっては二重装備になります。

採用してよいのは既存の自前UIへ認証機能を後から載せる改修案件

Fortifyが効くのは次の3条件がそろうときです。第一に、画面のデザインが確定済みで、キット付属のUIを剥がすより自前で組むほうが工数を読める場合。第二に、認証要件が登録・ログイン・リセット・メール確認・2要素認証・パスキーの範囲へ収まる場合。第三に、既存アプリへ後から認証を足すため、新規作成からやり直せない場合です。

とくに三番目は受託開発で頻繁に起きます。稼働中のシステムへ会員機能を足す改修では作り直す選択肢がなく、パスワードリセットやメール確認を自作すると有効期限や再送制御といった細部が工数増加の要因です。Fortifyはその部分を既製の実装で埋めながら、画面だけ既存のデザインへ寄せられます。会員基盤そのものの設計から相談したい場合は、認証基盤・ID管理システム開発の観点で要件を切り分けると判断が早くなります。

見送るべきなのは新規作成の案件と管理者ログインだけを扱う要件

逆に、次の3つはFortifyを個別に入れる理由がありません。1つ目は、新規作成でスターターキットを選べる案件。キットは内部でFortifyを使っているため、追加インストールは不要どころか設定の二重管理になります。2つ目は、管理者が数名ログインするだけで登録もリセットもメール確認も要らない要件です。認証ファサードを直接使う数十行のほうが読みやすく、保守も軽くなります。

3つ目は、シングルサインオンやSAMLが要件へ入る案件です。FortifyはID連携を担当しないため、外部の認証基盤との接続は別の仕組みが要ります。加えて、SPAやモバイル向けにトークン発行だけが要る案件なら、必要なのはFortifyではなくSanctumです。責務が違うものを混ぜないでください。

Sanctum側の実装はLaravel Sanctumとは:SPAとモバイルのトークン認証とCookieセッションの使い分けにまとめました。

後付け導入作業で必ず確認する既存テーブルと認証ルートの衝突条件

既存アプリへ後から入れるときは、着手前に3点を確認します。まず、fortify:installが追加するマイグレーションとusersテーブルの現状です。2要素認証の秘密鍵やリカバリコードを保持する列が加わるため、同名の列があるなら調整が要ります。

次に、自作のログインやユーザー登録ルートとの衝突です。旧来の認証ルートが残っていると二重定義になるので、pathsで寄せるか旧ルートを削るかを先に決めます。最後にミドルウェアで、既定はwebグループが当たるため、独自のミドルウェアで包む構成では設定側へ足します。

よくある質問

Laravel FortifyとBreezeはどちらを使うべきですか?

画面まで含めた雛形が欲しいならスターターキット、処理だけが欲しいならFortifyです。Breeze自体が公式の推奨から外れているため、新規案件の比較対象は現行のスターターキットとFortifyの二択になります。既存アプリへ後から認証を足す改修ではFortifyが素直です。

Fortifyを入れるとログイン画面は自動で表示されますか?

表示されません。Fortifyはルートとコントローラだけを登録し、ビューを持たないためです。FortifyServiceProviderのbootでloginViewなどを登録し、返すBladeテンプレートを自分で用意してください。

Laravel SanctumとFortifyは併用できますか?

併用可能です。公式ドキュメントも、APIを提供するアプリやSPAのバックエンドでは両方を使う構成を想定しています。Sanctumがトークンとセッションの認証、Fortifyが登録やパスワードリセットの入口を担当し、ガードは既定のwebのまま組み合わせます。

Fortifyの2要素認証はSMSに対応していますか?

標準はTOTPのみです。認証アプリが生成する6桁のコードを検証する仕組みで、SMSによるワンタイムパスワードの送信機能は含まれません。SMSが要件なら、外部の配信サービスと自前の検証処理を組み合わせる形になります。

パスキーはどのバージョンから使えますか?

1.37.0(2026年4月28日)で追加されました。同じリリースでPHP 8.1とLaravel 10のサポートが終了したため、古い環境のままでは入れられません。既定値の修正が1.37.2、2要素認証まわりの修正が1.39.0で入っているので、新規に組むなら1.39系を前提にします。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.09 テックブログ IDCFクラウド(IDCフロンティア)不正アクセス・ランサムウェア:影響先・復旧・データは戻るか
  2. 2026.10.09 テックブログ ユニリタの不正アクセス:SaaS文書管理から漏れた顧客の認証情報と回す手順
  3. 2026.10.09 テックブログ ニッスイのサイバー攻撃で日水物流の入出荷停止|委託先クラウド障害に荷主が備える手順
  4. 2026.10.08 テックブログ 大阪公立大学のランサムウェア被害と仮想化基盤の停止|全授業休講に至った経緯とバックアップを守る設定
  5. 2026.10.09 テックブログ 京王電鉄のランサムウェア被害とグループ共通基盤:決済・ポイント・予約が止まった範囲と遮断の初動

RELATED POSTS 関連記事

目次