tall
tall copied to clipboard
Use concerns of laravel/ui
Many solutions of laravel are not accessible if you are using this preset (including security features).
For example the concern AuthenticatesUsers.
It ships with throttling login attempts and choosing a custom username() instead of only email.
Maybe the concerns should be added but then this preset or better laravel/ui cannot be uninstalled because the concerns are implemented there.
Choosing a custom username is as simple as editing the email fields there, just as you'd override the username() method in the default LoginController - I don't think that concern is missing functionality necessarily.
However, I will admit that we forgot to implement the login throttling behaviour that comes from the ThrottleLogins trait - we'll have to see about adding that back and see what the nicest way will be.
Choosing a custom
usernameis as simple as editing theusername()method in the defaultLoginController- I don't think that concern is missing functionality necessarily.However, I will admit that we forgot to implement the login throttling behaviour that comes from the
ThrottleLoginstrait - we'll have to see about adding that back and see what the nicest way will be.
Did this ever materialize? Searching for any sort of login-throttling behaviour in vain. It's not like we can just use throttling middleware from the routes file as livewire POSTs to it's own endpoints and not the component/page route. And the "ThrottleLogins" trait requires use of $request, which we don't have access to in livewire components. Any suggestions?
You can update your livewire config:
- Publish the config using
php artisan livewire:publish - Edit
config/livewire.php
return [
// ...
'middleware_group' => ['web', 'throttle:5,1'],
// ...
];
This will then be applied to all Livewire Components
Edit: Otherwise you can take a look at traits where you implement your throttle logic: https://laravel-livewire.com/docs/2.x/traits
@Jubeki thanks for the pointers! Will the throttling apply to the livewire endpoint (/livewire/*) or just the frontend route (on which you could already use regular laravel route throttling)?
Ideally you would only want very conservative/slow throttling applied to the login/register methods.
The throttling should be applied to the /livewire endpoint. Otherwise the following authorization example would be meaningless: https://laravel-livewire.com/docs/2.x/authorization
You could make a feature request on https://github.com/livewire/livewire for middleware for components. There is also the livewire discord for questions and ideas: https://discord.gg/livewire
I will tag Caleb here. Maybe he has some ideas. cc @calebporzio
@Jubeki great solution as a fail-safe on all routes but blows up a route with an ugly 403 error. Instead, I found a way to use a validation rule for more specific throttling and a graceful way to relay that to the end user.
@glaesser thanks to @stevebauman I was able to come up with a custom throttle validation rule that follows the laravel ui methodology and by extension a custom Lockout Event since both required access to the Request object.
Give this a shot when you get the chance:
App\Events\Lockout.php
<?php
namespace App\Events;
class Lockout
{
public $key, $identifier;
public function __construct(string $key, string $identifier = '')
{
$this->key = $key;
$this->identifier = $identifier;
}
}
App\Rules\Throttle.php
<?php
namespace App\Rules;
use Illuminate\Cache\RateLimiter;
use Illuminate\Contracts\Validation\Rule;
class Throttle implements Rule
{
protected $throttleKey, $maxAttempts, $decayInMinutes, $event;
public function __construct($throttleKey, $maxAttempts, $decayInMinutes, $event = null)
{
$this->throttleKey = $throttleKey;
$this->maxAttempts = $maxAttempts;
$this->decayInMinutes = $decayInMinutes;
$this->event = $event;
}
public function passes($attribute, $value): bool
{
if ($this->hasTooManyAttempts()) {
if($this->event) {
event(new $this->event($this->throttleKey, $value));
}
return false;
}
$this->incrementAttempts();
return true;
}
public function message()
{
$seconds = $this->limiter()
->availableIn($this->throttleKey);
return trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]);
}
protected function hasTooManyAttempts()
{
return $this->limiter()->tooManyAttempts(
$this->throttleKey, $this->maxAttempts
);
}
protected function limiter()
{
return app(RateLimiter::class);
}
protected function incrementAttempts()
{
$this->limiter()->hit(
$this->throttleKey, $this->decayInMinutes * 60
);
}
}
App\Http\Livewire\Auth\Login.php
<?php
namespace App\Http\Livewire\Auth;
use App\Events\Lockout;
use App\Rules\Throttle;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Livewire\Component;
class Login extends Component
{
public $ip, $email, $password, $remember = false;
protected function rules(): array
{
return [
'email' => [
'required',
'email',
new Throttle($this->throttleKey(), 5, 1, Lockout::class)],
'password' => ['required'],
];
}
public function mount(Request $request)
{
$this->ip = $request->ip();
}
public function login()
{
$input = $this->validate();
if (Auth::attempt($input, $this->remember)) {
session()->regenerate();
return redirect()->intended(route('home'));
}
return $this->addError('password', trans('auth.failed'));
}
protected function throttleKey()
{
return Str::lower("$this->email|$this->ip");
}
public function render()
{
return view('livewire.auth.login');
}
}