最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

php - Laravel 11: Custom Password Broker Ignores My Tenant-Aware Repository - Stack Overflow

programmeradmin4浏览0评论

I'm implementing multi-tenancy in my Laravel 11 application, so I need a tenant_id column in the password_reset_tokens table. I've created a custom PasswordBrokerManager and a custom TenantAwareDatabaseTokenRepository, but Laravel still uses the default DatabaseTokenRepository—so the tenant_id never gets inserted.

1. config/auth.php

return [
    'defaults' => [
        'guard' => env('AUTH_GUARD', 'web'),
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],
    'guards' => [
        'web' => [
            'driver'   => 'session',
            'provider' => 'users',
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model'  => App\Models\User::class,
        ],
    ],
    'passwords' => [
        'users' => [
            'driver'   => 'custom',  // I made sure to set this
            'provider' => 'users',
            'table'    => 'password_reset_tokens',
            'expire'   => 60,
            'throttle' => 60,
        ],
    ],
];

I ran php artisan config:clear && php artisan cache:clear and verified with dd(config('auth.passwords.users')) that it shows "driver" => "custom".

2. AppServiceProvider

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Auth\Passwords\PasswordBrokerManager;
use App\Extensions\CustomPasswordBrokerManager;
use Illuminate\Support\Facades\Log;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        Log::debug('AppServiceProvider: register() is being called...');

        // Bind my custom password broker manager
        $this->app->singleton(PasswordBrokerManager::class, function ($app) {
            Log::debug('Binding CustomPasswordBrokerManager...');
            return new CustomPasswordBrokerManager($app);
        });
    }

    public function boot(): void
    {
        Log::debug('AppServiceProvider: boot() is being called...');
    }
}

The logs for AppServiceProvider show up, but when I trigger a password reset, my custom code is ignored.

3. CustomPasswordBrokerManager

<?php

namespace App\Extensions;

use Illuminate\Auth\Passwords\PasswordBrokerManager;
use Illuminate\Auth\Passwords\TokenRepositoryInterface;
use App\Repositories\TenantAwareDatabaseTokenRepository;
use App\Models\Tenant;
use Illuminate\Support\Facades\Log;
use RuntimeException;

class CustomPasswordBrokerManager extends PasswordBrokerManager
{
    public function __construct($app)
    {
        Log::debug('CustomPasswordBrokerManager: constructor called!');
        parent::__construct($app);
    }

    protected function createTokenRepository(array $config): TokenRepositoryInterface
    {
        Log::debug('CustomPasswordBrokerManager: createTokenRepository called! driver=' . ($config['driver'] ?? ''));

        // 1) DB connection
        $connection = $this->app['db']->connection($config['connection'] ?? null);

        // 2) Table name
        $table = $config['table'];

        // 3) Laravel app key
        $hashKey = $this->app['config']['app.key'];

        // 4) Expiry & throttle
        $expire   = $config['expire'];
        $throttle = $config['throttle'] ?? 60;

        // 5) Current tenant
        $tenant = Tenant::current();
        if (! $tenant || ! $tenant->id) {
            throw new RuntimeException('No tenant found. Tenant ID cannot be null.');
        }

        // 6) Return custom repository
        return new TenantAwareDatabaseTokenRepository(
            $connection,
            $this->app['hash'],
            $table,
            $hashKey,
            $expire,
            $throttle,
            $tenant->id
        );
    }
}

I never see the “createTokenRepository called!” log in my laravel.log, meaning Laravel won’t enter this code.

4. TenantAwareDatabaseTokenRepository

<?php

namespace App\Repositories;

use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Support\Carbon;

class TenantAwareDatabaseTokenRepository extends DatabaseTokenRepository
{
    protected mixed $tenantId;

    public function __construct(
        ConnectionInterface $connection,
        Hasher $hasher,
        string $table,
        string $hashKey,
        int $expires = 60,
        int $throttle = 60,
        mixed $tenantId = null
    ) {
        parent::__construct($connection, $hasher, $table, $hashKey, $expires, $throttle);
        $this->tenantId = $tenantId;
    }

    protected function getPayload($email, $token): array
    {
        return [
            'email'      => $email,
            'tenant_id'  => $this->tenantId,
            'token'      => $this->getHasher()->make($token),
            'created_at' => Carbon::now(),
        ];
    }
}

This class includes tenant_id in the insert, but Laravel keeps using the default DatabaseTokenRepository.

5. Error & Stack Trace

When I request a password reset, I get:

SQLSTATE[HY000]: General error: 1364 Field 'tenant_id' doesn't have a default value
Illuminate\Auth\Passwords\DatabaseTokenRepository->create()

It shows DatabaseTokenRepository is being called, not TenantAwareDatabaseTokenRepository.

6. Controller Snippet

public function store(Request $request): Responsable
{
    $request->validate(['email' => 'required|email']);

    $status = $this->broker()->sendResetLink($request->only('email'));

    // ...
}

protected function broker(): PasswordBroker
{
    return Password::broker(config('fortify.passwords')); 
    // config('fortify.passwords') is "users"
}

7. Things I Tried

  • Double-checked 'driver' => 'custom' in config/auth.php.
  • Ran php artisan config:clear && php artisan cache:clear.
  • Logged in CustomPasswordBrokerManager::createTokenRepository(), but it never fires.
  • Confirmed 'passwords' => 'users' in config/fortify.php.
  • Verified logs in AppServiceProvider do show up.

8. Environment

  • Laravel: 11.x
  • PHP: 8.3
  • MySQL/MariaDB
  • Jetstream + Fortify

Question

How do I ensure Laravel actually uses my CustomPasswordBrokerManager and TenantAwareDatabaseTokenRepository instead of defaulting to DatabaseTokenRepository? Any help is appreciated.

I'm implementing multi-tenancy in my Laravel 11 application, so I need a tenant_id column in the password_reset_tokens table. I've created a custom PasswordBrokerManager and a custom TenantAwareDatabaseTokenRepository, but Laravel still uses the default DatabaseTokenRepository—so the tenant_id never gets inserted.

1. config/auth.php

return [
    'defaults' => [
        'guard' => env('AUTH_GUARD', 'web'),
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],
    'guards' => [
        'web' => [
            'driver'   => 'session',
            'provider' => 'users',
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model'  => App\Models\User::class,
        ],
    ],
    'passwords' => [
        'users' => [
            'driver'   => 'custom',  // I made sure to set this
            'provider' => 'users',
            'table'    => 'password_reset_tokens',
            'expire'   => 60,
            'throttle' => 60,
        ],
    ],
];

I ran php artisan config:clear && php artisan cache:clear and verified with dd(config('auth.passwords.users')) that it shows "driver" => "custom".

2. AppServiceProvider

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Auth\Passwords\PasswordBrokerManager;
use App\Extensions\CustomPasswordBrokerManager;
use Illuminate\Support\Facades\Log;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        Log::debug('AppServiceProvider: register() is being called...');

        // Bind my custom password broker manager
        $this->app->singleton(PasswordBrokerManager::class, function ($app) {
            Log::debug('Binding CustomPasswordBrokerManager...');
            return new CustomPasswordBrokerManager($app);
        });
    }

    public function boot(): void
    {
        Log::debug('AppServiceProvider: boot() is being called...');
    }
}

The logs for AppServiceProvider show up, but when I trigger a password reset, my custom code is ignored.

3. CustomPasswordBrokerManager

<?php

namespace App\Extensions;

use Illuminate\Auth\Passwords\PasswordBrokerManager;
use Illuminate\Auth\Passwords\TokenRepositoryInterface;
use App\Repositories\TenantAwareDatabaseTokenRepository;
use App\Models\Tenant;
use Illuminate\Support\Facades\Log;
use RuntimeException;

class CustomPasswordBrokerManager extends PasswordBrokerManager
{
    public function __construct($app)
    {
        Log::debug('CustomPasswordBrokerManager: constructor called!');
        parent::__construct($app);
    }

    protected function createTokenRepository(array $config): TokenRepositoryInterface
    {
        Log::debug('CustomPasswordBrokerManager: createTokenRepository called! driver=' . ($config['driver'] ?? ''));

        // 1) DB connection
        $connection = $this->app['db']->connection($config['connection'] ?? null);

        // 2) Table name
        $table = $config['table'];

        // 3) Laravel app key
        $hashKey = $this->app['config']['app.key'];

        // 4) Expiry & throttle
        $expire   = $config['expire'];
        $throttle = $config['throttle'] ?? 60;

        // 5) Current tenant
        $tenant = Tenant::current();
        if (! $tenant || ! $tenant->id) {
            throw new RuntimeException('No tenant found. Tenant ID cannot be null.');
        }

        // 6) Return custom repository
        return new TenantAwareDatabaseTokenRepository(
            $connection,
            $this->app['hash'],
            $table,
            $hashKey,
            $expire,
            $throttle,
            $tenant->id
        );
    }
}

I never see the “createTokenRepository called!” log in my laravel.log, meaning Laravel won’t enter this code.

4. TenantAwareDatabaseTokenRepository

<?php

namespace App\Repositories;

use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Support\Carbon;

class TenantAwareDatabaseTokenRepository extends DatabaseTokenRepository
{
    protected mixed $tenantId;

    public function __construct(
        ConnectionInterface $connection,
        Hasher $hasher,
        string $table,
        string $hashKey,
        int $expires = 60,
        int $throttle = 60,
        mixed $tenantId = null
    ) {
        parent::__construct($connection, $hasher, $table, $hashKey, $expires, $throttle);
        $this->tenantId = $tenantId;
    }

    protected function getPayload($email, $token): array
    {
        return [
            'email'      => $email,
            'tenant_id'  => $this->tenantId,
            'token'      => $this->getHasher()->make($token),
            'created_at' => Carbon::now(),
        ];
    }
}

This class includes tenant_id in the insert, but Laravel keeps using the default DatabaseTokenRepository.

5. Error & Stack Trace

When I request a password reset, I get:

SQLSTATE[HY000]: General error: 1364 Field 'tenant_id' doesn't have a default value
Illuminate\Auth\Passwords\DatabaseTokenRepository->create()

It shows DatabaseTokenRepository is being called, not TenantAwareDatabaseTokenRepository.

6. Controller Snippet

public function store(Request $request): Responsable
{
    $request->validate(['email' => 'required|email']);

    $status = $this->broker()->sendResetLink($request->only('email'));

    // ...
}

protected function broker(): PasswordBroker
{
    return Password::broker(config('fortify.passwords')); 
    // config('fortify.passwords') is "users"
}

7. Things I Tried

  • Double-checked 'driver' => 'custom' in config/auth.php.
  • Ran php artisan config:clear && php artisan cache:clear.
  • Logged in CustomPasswordBrokerManager::createTokenRepository(), but it never fires.
  • Confirmed 'passwords' => 'users' in config/fortify.php.
  • Verified logs in AppServiceProvider do show up.

8. Environment

  • Laravel: 11.x
  • PHP: 8.3
  • MySQL/MariaDB
  • Jetstream + Fortify

Question

How do I ensure Laravel actually uses my CustomPasswordBrokerManager and TenantAwareDatabaseTokenRepository instead of defaulting to DatabaseTokenRepository? Any help is appreciated.

Share Improve this question asked 15 hours ago Joe NyamburaJoe Nyambura 2035 silver badges14 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

I think the provider you want to override is auth.password (Github):

$this->app->singleton('auth.password', function ($app) {
    Log::debug('Binding CustomPasswordBrokerManager...');
    return new CustomPasswordBrokerManager($app);
});
发布评论

评论列表(0)

  1. 暂无评论