Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

rg83's avatar
Level 4

Jetstream Two Factor Authentication

Hi,

I recently enabled two factor authentication on my laravel app using Jetstream. During testing i noticed that using a recovery code to sign in actually regenerates new recovery codes. Is this normal? The user is never even notified that this happened and probably won't record the new codes. At the moment i'm considering preventing them from being regenerated by altering the store method in the TwoFactorAuthenticatedSessionController class that calls $user->replaceRecoveryCode($code);. An alternative would be to notify the user that they have been changed. I guess i'm looking for advice on what path I should take and why they regernate the codes when you use one to sign in.

1 like
3 replies
fylzero's avatar
fylzero
Best Answer
Level 67

@rg83 This is actually part of the Fortify code that Jetstream uses.

vendor/laravel/fortify/src/Http/Controllers/TwoFactorAuthenticatedSessionController.php:59

/**
 * Attempt to authenticate a new session using the two factor authentication code.
 *
 * @param  \Laravel\Fortify\Http\Requests\TwoFactorLoginRequest  $request
 * @return mixed
 */
public function store(TwoFactorLoginRequest $request)
{
    $user = $request->challengedUser();

    if ($code = $request->validRecoveryCode()) {
        $user->replaceRecoveryCode($code);  // Recovery code regenerated here
    } elseif (! $request->hasValidCode()) {
        return app(FailedTwoFactorLoginResponse::class);
    }

    $this->guard->login($user, $request->remember());

    $request->session()->regenerate();

    return app(TwoFactorLoginResponse::class);
}

This was part of Taylor's initial commit on this repo so there isn't anything documented about the decision to do this here.

I agree this isn't made clear to the user with the stock Jetstream setup. I went ahead and created an issue here:

https://github.com/laravel/fortify/issues/255

That all said, if you want to override the reset functionality... just copy this into a file called app/Http/Controllers/TwoFactorAuthenticatedSessionController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use Laravel\Fortify\Contracts\FailedTwoFactorLoginResponse;
use Laravel\Fortify\Contracts\TwoFactorLoginResponse;
use Laravel\Fortify\Http\Requests\TwoFactorLoginRequest;

class TwoFactorAuthenticatedSessionController extends Controller
{
    public function store(TwoFactorLoginRequest $request)
    {
        $user = $request->challengedUser();

        if ($code = $request->validRecoveryCode()) {
            // $user->replaceRecoveryCode($code);
        } elseif (! $request->hasValidCode()) {
            return app(FailedTwoFactorLoginResponse::class);
        }

        $this->guard->login($user, $request->remember());

        $request->session()->regenerate();

        return app(TwoFactorLoginResponse::class);
    }
}

This will override that method because of PSR-4 auto-loading. Having that line commented out will prevent the reset from occurring.

2 likes
rg83's avatar
Level 4

Hey,

So I tried doing this and it seems like original method from the vendor class is running. Is there anything special I need to do in order to ensure the method from my class runs instead?

mariiamozgunova's avatar

@rg83 it works for me when I add this line to the AppServiceProvider in my application to the register() method:

$this->app->singleton(TwoFactorAuthenticatedSessionController::class, CustomTwoFactorAuthenticatedSessionController::class);
1 like

Please or to participate in this conversation.