Laravel custom Login Throttling - laravel

I want to add Login Throttling to my custom login code. I use an email and a password for the login. Here is what i did:
I added
use ThrottlesLogins;
protected $maxLoginAttempts=3;
protected $lockoutTime=20;
at the beginning of my class, and then in my login method i did:
public function login(Request $request)
{
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
If the login fails i add
$this->incrementLoginAttempts($request);
if it succeed I add
$this->clearLoginAttempts($request);
The problem though i'm getting:
Undefined method LoginController::username
I'm guessing it is because i use an email instead of a username for login. What is the solution for this?

This particular trait, ThrottlesLogins, is meant to be used for the default LoginController that uses the AuthenticatesUsers trait, which provides a username method.
You can define this method yourself:
/**
* Get the login username to be used by the controller.
*
* #return string
*/
public function username()
{
return 'email';
}

Related

how to change redirect after auth Email verification in laravel 8?

I have 2 condition after successful registration with email verification.
If the new user is select plan from home page, redirects to registration page submits the form. then will get Email verfication link, and after email verified I want to redirect directly to checkout. Plan id will be saving session , so I can get all the details of plan.
If the new user is not select plan from home page, then he can sign up and redirects to dashboard
But in laravel after email verfication always redirects to home page. But I dont want to redirect to home page again.
How can be this done? Wher can do the coding part?
Verification Controller
use VerifiesEmails;
/**
* Where to redirect users after verification.
*
* #var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
protected function verified(Request $request)
{
$request->session()->flash('alert','Your Email is verfied');
}
Routes
public function emailVerification()
{
return function () {
$this->get('email/verify', 'Auth\VerificationController#show')->name('verification.notice');
$this->get('email/verify/{id}/{hash}', 'Auth\VerificationController#verify')->name('verification.verify');
$this->post('email/resend', 'Auth\VerificationController#resend')->name('verification.resend');
};
}
Add a method called redirectTo(). It will be called if it exists.
public function redirectTo()
{
// put your routing logic here
}
The function should return a string of the url to go to.
The best option that worked for me in Laravel 9* was to define a new public constant in the RouteServiceProvider right under or above the HOME
e.g
Location: app\Providers\RouteServiceProvider
public const SUB = '/account/subscription';
Then go to VerificationController
Location: app\Http\Controllers\Auth\VerificationController
and change
protected $redirectTo = RouteServiceProvider::HOME;
to
protected $redirectTo = RouteServiceProvider::SUB;
If you are using Fortify, I think the more Laravel way to solve this, is to create a Response handler.
Laravel will, when an email is verified, generate a VerifyEmailResponse. So to modify that logic, you would create a class and make that the singleton.
<?php
namespace App\Http\Responses;
use Laravel\Fortify\Contracts\VerifyEmailResponse as VerifyEmailResponseContract;
class VerifyEmailResponse implements VerifyEmailResponseContract
{
public function toResponse($request)
{
// Your custom logic returning redirect(), \Illuminate\Routing\Redirector or \Illuminate\Http\RedirectResponse
}
}
And in your FortifyServiceProvider class:
use Laravel\Fortify\Contracts\VerifyEmailResponse as VerifyEmailResponseContract;
public function boot()
{
// logic
$this->app->singleton(VerifyEmailResponseContract::class, VerifyEmailResponse::class);
// logic
}
Couple approaches from what I can see...
In your controller actions where you are routing them to
Auth\VerificationController#verify do the check to make sure a flag
isn't already set, set the flag if it isn't set, put your redirect
after that logic. So it would be something like this for a route...
return redirect()->route('route.name', [$veriYouWantToSendAlong]);
Or to a static page with errors.
return redirect()->route('page/name')->withErrors(['Some error here']);
In your Laravel auth controller should be a protected $redirectTo = '/home'; action you can change that to dashboard or wherever you'd like to send them after log in.
Maybe add this in the Routes/web.php.
And change the /dashboard to whatever you like :).
Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
$request->fulfill();
return redirect('/dashboard');
})->middleware(['auth', 'signed'])->name('verification.verify');

How to perform addition action on login in Laravel 5.8?

In Laravel 5.8 the Auth\LoginController is just:
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/my-team';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
On login I want to perform some custom actions for the user, but I can't figure out where I can place this code. The documentation doesn't seem to help.
Is there a method I can overwrite/extend with my own?
Laravel 5.2 used to have a login() method in the Auth controller where I could just write additional code.
You can override the login() method in LoginController. As you said, in Laravel 5.8 the login() method doesn't exist, but you can define it yourself. The newly defined login() method will override the default one and then you can do whatever extra you want to after or before the user signs in. Here is a snippet from Laracasts:
public function login(Request $request)
{
$this->validateLogin($request);
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if(Auth::attempt(['email' => $request->email, 'password' => $request->password, 'is_activated' => 1])) {
// return redirect()->intended('dashboard');
} else {
$this->incrementLoginAttempts($request);
return response()->json([
'error' => 'This account is not activated.'
], 401);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
Just go through it and see what has been done there. In short, you can modify the login() method to do whatever you want before or after a user signs in.
There are two functions provided by the AuthenticatesUsers trait. You can customize those in the login controller.
Login Form
public function showLoginForm()
{
return view('auth.login');
}
Handle Login
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
You can place these two functions in the login controller and make changes as you want.
The best place to add your custom actions is to override authenticated method in your LoginController
protected function authenticated(Request $request, $user)
{
// Your code
}
Just go to where the Illuminate\Foundation\Auth\AuthenticatesUsers trait is located and you will find all the methods you want
it's located in :
vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers
Or you can overwrite it in the LoginController to do what ever you want

How to Verify Email Without Asking the User to Login to Laravel

I am developing a Laravel application. My application is using Laravel built-in auth feature. In the Laravel auth when a user registers, a verification email is sent. When a user verifies the email click on the link inside the email, the user has to login again to confirm the email if the user is not already logged in.
VerificationController
class VerificationController extends Controller
{
use VerifiesEmails, RedirectsUsersBasedOnRoles;
/**
* Create a new controller instance.
* #return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
public function redirectPath()
{
return $this->getRedirectTo(Auth::guard()->user());
}
}
I tried commenting on this line.
$this->middleware('auth');
But it's s not working and instead, throwing an error. How can I enable Laravel to be able to verify email even if the user is not logged in?
First, remove the line $this->middleware('auth');, like you did.
Next, copy the verify method from the VerifiesEmails trait to your VerificationController and change it up a bit. The method should look like this:
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new Verified($user));
return redirect($this->redirectPath())->with('verified', true);
}
This overrides the method in the VerifiesUsers trait and removes the authorization check.
Security (correct me if I'm wrong!)
It's still secure, as the request is signed and verified. Someone could verify another user's email address if they somehow gain access to the verification email, but in 99% of cases this is hardly a risk at all.
Here's a more future proof solution to the problem:
class VerificationController extends Controller
{
// …
use VerifiesEmails {
verify as originalVerify;
}
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth'); // DON'T REMOVE THIS
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
/**
* Mark the authenticated user's email address as verified.
*
* #param Request $request
* #return Response
*
* #throws AuthorizationException
*/
public function verify(Request $request)
{
$request->setUserResolver(function () use ($request) {
return User::findOrFail($request->route('id'));
});
return $this->originalVerify($request);
}
}
So when an email confirmation link is clicked by an unauthenticated user the following will happen:
User will be redirected to the login view 1
User enters credentials; logs in successfully 2
User will be redirect back to the email confirmation URL
Email will be marked as confirmed
1 The email will not be marked as confirmed at this point.
2 The user may enter bad credentials multiple times. As soon as he enters the correct credentials he will be redirected to the intended email confirmation URL.
// For Laravel 6 and Above
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\Request;
use App\User;
// comment auth middleware
//$this->middleware('auth');
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new Verified($user));
return redirect($this->redirectPath())->with('verified', true);
}
Solution to allow email verification for users who are not logged in (i.e. without auth):
Changes to: app/Http/Controllers/Auth/VerificationController.php:
$this->middleware('auth'); to $this->middleware('auth')->except('verify');
Copy verify() method from the VerifiesEmails trait.
Edit verify method to work without expected $request->user() data.
My verify() method in the VerificationController looks like this:
public function verify(\Illuminate\Http\Request $request)
{
$user = User::find($request->route('id'));
if ($request->route('id') != $user->getKey()) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new Verified($user));
return redirect()->route('login')->with('verified', true);
}
Signed middleware
Laravel uses a middleware named signed to check the integrity of URLs that were generated by the application. Signed checks whether the URL has been changed since it was created. Try changing the id, expiry time or the signature in the url and it will lead to an error - very effective and useful middleware to protect the verify() method
For more information: https://laravel.com/docs/8.x/urls#signed-urls
(Optional)
I redirected my users to the login route, rather than the intended route for two reasons. 1) After login, it would try to redirect the user to the email verification link, leading to an error; 2) I wanted to use the verified true flash data that was attached to the redirect, to show an alert on the login page, if the user had successfully verified their email address.
Example of my login page alert:
#if(session()->has('verified'))
<div class="alert alert-success">Your email address has been successfully verified.</div>
#endif
Suggestions
If you have any suggestions on how I could improve this code, please let me know. I'd be happy to edit this answer.
You should not remove $this->middleware('auth') altogether as that will effect the redirects. If you remove it, the unauthenticated users will be redirected to "/email/verify" instead of "/login"
so $this->middleware('auth'); will be changed to $this->middleware('auth')->except('verify'); in "VerificationController"
Also copy the "verify" function from "VerifiesEmails" into "VerificationController"
add these two lines of code at the top of the function
$user = User::find($request->route('id'));
auth()->login($user);
so you are logging in the user programmatically and then performing further actions
Here's my take on the situation. Verification requires user to login before it can complete the verification, so we can override the verify function and login user using ID we received in the link. It is safe cause verify function is not called if Laravel can't verify the signature from URL so even if someone temper the URL they won't be able to bypass it.
Go to your VerificationController and add the following function at the end of the file.
public function verify(Request $request)
{
if (!auth()->check()) {
auth()->loginUsingId($request->route('id'));
}
if ($request->route('id') != $request->user()->getKey()) {
throw new AuthorizationException;
}
if ($request->user()->hasVerifiedEmail()) {
return redirect($this->redirectPath());
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect($this->redirectPath())->with('verified', true);
}
Note
Make sure you have same_site value in 'config/session.php' set to 'lax'. If it is set to 'strict' then it won't persist session if you were redirected from another site. For example, if you click a verification link from Gmail then your session cookie won't persist, so it won't redirect you to dashboard, but it sets 'email_verified_at' field in the database marking the verification successful. The user won't get any idea what was happened because it will redirect the user to the login page. When you have set it to 'strict', it will work if you copy the verification link directly in the browser address bar but not if the user clicks the link from the Gmail web client because it uses redirect to track the link.
if you want to active user account without login you can do that in 2 steps
1- Remove or comment Auth middleware in VerificationController
Example below:
public function __construct()
{
//$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
2- since verify route passing the {id} you can just edit verify function to find the user by the route id request like code below :
file path : *:\yourproject\vendor\laravel\framework\src\Illuminate\Foundation\Auth\VerifiesEmails.php
$user = User::findOrfail($request->route('id'));
Complete example
public function verify(Request $request)
{
$user = User::findOrfail($request->route('id'));
if (! hash_equals((string) $request->route('id'), (string) $user->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->hasVerifiedEmail()) {
return redirect($this->redirectPath())->with('verified', true);
}
if ($user->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect($this->redirectPath())->with('registered', true);
}
I change EmailVerificationRequest but i now this is wrong, any way it's work.
Warning
This change on the vendor
protected $user;
public function authorize()
{
$this->user = \App\Models\User::find($this->route('id'));
if ($this->user != null){
if (! hash_equals((string) $this->route('id'),
(string) $this->user->getKey())) {
return false;
}
if (! hash_equals((string) $this->route('hash'),
sha1($this->user->getEmailForVerification()))) {
return false;
}
return true;
}
return false;
}
To use inner laravel logic (without overriding the logic), we simply create $request->user() and call trait's verify method. And manually sign in the user when the verification is successful.
use VerifiesEmails {
verify as parentVerify;
}
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!$user) return abort(404);
$request->setUserResolver(function () use($user) {
return $user;
});
return $this->parentVerify($request);
}
public function verified(Request $request)
{
Auth::login($request->user());
}

Laravel Authentication Reset

I'm having difficulty how do I get the user id based on the token generated by laravel? I'm not very familiar with framewrok can anyone help me?
class ForgotPasswordController extends Controller
{
use SendsPasswordResetEmails;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
}
}
ResetPasswordController
class ResetPasswordController extends Controller
{
use ResetsPasswords;
protected $redirectTo = '/login';
public function __construct()
{
$this->middleware('guest');
}
protected function guard()
{
return Auth::guard('user');
}
public function broker()
{
return Password::broker('users');
}
public function showResetForm(Request $request, $token = null)
{
var_dump($request->email);
var_dump( $request->token);
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
}
how to update password? I only have the token received by email?
Check this steps so you can understand better how laravel auth works.
Generate the auth scaffolding with: php artisan make:auth
Run the created migrations:php artisan migrate
Check your route list with php artisan route:list, you can see that routes were created too when you ran make:auth command.
Take note of the controllers and methods the Auth routes are calling.
Go to the routes web.php file. See the Auth::routes();, this generate the default Laravel Auth routes, if you don't need em all, comment or delete this line and add the required Lines.
If you took note of the controllers and methods, you can see what's been called on each route. In this particular case, i think you'd be looking for ResetPasswordController which uses ResetsPasswords from vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php there you can see the resets method this is what you'll have to override on your ResetPasswordController.

Authentication on username instead of email

I want to check the user on username and password instead of email and password. And I found that this works:
public function login(){
if(!Auth::attempt(request()->only('username', 'password')))
{
return redirect('login');
}
return redirect('login');
}
But I liked how the original login() method gave the error 'These credentials do not match our records.'.
I would like to know if this is the "Laravel way" to doing this because I'd think it would be a one line code to change it to username. If this is the right way how would I get the error back to show up?
Instead of that I removed the login method and added this to LoginController.php:
/**
* Override the username method used to validate login
*
* #return string
*/
public function username()
{
return 'username';
}
That was all there is to it it works great!

Resources