Are you using out of the box authentication, and which version?
User Account Status
I need to find out how to run a custom validation rule for my login form. This rule needs to take place before it checks the password supplied by the login page is correct. This rule is to check the status of user attempting to login. If the user has a status_id of 2 then they can continue with the next validation rule but if they are anything other than two I need it to error and report back to the user why they are not allowed to login and have the message be a custom message based on what that status_id was.
Where can I place this bit of logic? And how would this be done?
@Snapey I am using out of the box authentication and I'm using Laravel 5.2
This is pretty easy to do (as long as it's not a hard requirement that you have to check this before the password has been checked). If you are using Laravel's auth controllers (via the auth:make command), we can do this pretty simply by overriding the 'authenticated' method within your 'app/Http/Controllers/Auth/LoginController.php` file:
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
if ($user->status_id == 2) {
$message = 'Some message about status';
// Log the user out.
$this->logout($request);
// Return them to the log in form.
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([
// This is where we are providing the error message.
$this->username() => $message,
]);
}
}
The authenticated method will get called after a user has been successfully authenticated in the system, and (conveniently) we get access to both the $request data and the $user data. Here we can check to see if the user has a status_id of 2 (or whatever it may be). If so we simply log the user out (so they cannot type in the URL to get around the log in) and return them to the sign in page with an error message about their account status.
Update: If you use this approach, don't forget to add use Illuminate\Http\Request at the top of the controller file.
The concept is similar if you are manually authenticating users, and the basic idea is:
if (auth()->attempt(['email' => '[email protected]','password' => 'test'])) {
if (auth()->user()->status_id == 2) {
auth()->logout();
// Handle the error here.
}
}
My only thought is if they have a status of 1 (not validated), 3 (banned), 4(suspended) or etc. Why would they get logged in the first place? This is why I was trying to make a point of having it done before it logs the user in because if they are 1,3, or 4 an error message will be returned back telling them why they can't login.
if (auth()->attempt(['email' => '[email protected]', 'password' => 'test', 'status_id' => 2])) {
// Adding the staus_id is 2 check will fail authentication if not 2, You can also use validation too.
}
// Notice the exists rule goes exists: table name, column, another column, value another column must equal
$this->validate($request, [
'email' => 'required|email|exists:users,email,status_id,2',
'password' => 'required',
]);
https://laravel.com/docs/5.2/validation#introduction
Read a bit more, but you can set custom error messages to (then I suggest using a form request, cleaner then in your controller IMO).
I use the above validation a lot. Then you NOW 99% that if validation fails it is because the user put the wrong Password in.
if (auth()->attempt(['email' => '[email protected]', 'password' => 'test', 'status_id' => 2])) {
// User is logged in and do what you need.
}
// Password more then likely failed the auth attempt as we
// know the email exists and status_id is equal to 2 from the validation rule.
return back()->withErrors('Sorry, your password was incorrect');
I'm just trying to understand something though. That status_id is from the database, correct?
All of the suggestions posted so far would use the status_id from your database.
Look at it this way. First you establish their identity, then you tell them what is wrong and let them do something about it.
@johnmkoster I looked at your example but there is no login controller. Mine is a Laravel 5.2 setup.
In App/Http/Controllers/Auth/AuthController.php
Yep, it would be in app/Http/Controllers/Auth/AuthController.php. You will also need to change the username() method to loginUsername():
protected function authenticated(Request $request, $user)
{
if ($user->status_id == 2) {
$message = 'Some message about status';
// Log the user out.
$this->logout($request);
// Return them to the log in form.
return redirect()->back()
->withInput($request->only($this->loginUsername(), 'remember'))
->withErrors([
// This is where we are passing the message back.
$this->loginUsername() => $message,
]);
}
}
Tested, working on 5.2
So I've done this and I'm not so sure this will work. Because if they don't have an acceptable status_id why would they have been logged into the system anyway.
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
if ($user->status_id == 1) {
$message = 'This user account has yet to be unverified, Please verify this account.';
} elseif ($user->status_id == 3) {
$message = 'This user account is inactive.';
} elseif ($user->status_id == 4) {
$message = 'This user account has been suspended.';
} elseif ($user->status_id == 5) {
$message = 'This user account has been banned from logging in.';
}
// Log the user out.
$this->logout($request);
// Return them to the log in form.
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([
// This is where we are providing the error message.
$this->loginUsername() => $message,
]);
}
}
Doing that will cause all login attempts to fail since Laravel will use the return value of authenticated to redirect users once their identity has been confirmed. Because of this, we need to make sure that when we log out a user and redirect them back to the login page because of their account status, it has to be inside some condition that checks their status. A trick I used on an application was just to store the statuses in and array and check to see if the user's status was present in the array:
protected function authenticated(Request $request, $user)
{
// Holds all of our messages related to account statuses.
// All of the statuses we want to check are the keys
// within the array. This will let us search the
// array easily to see if a user can log in!
$statuses = [
1 => 'This user account has yet to be unverified, Please verify this account.',
3 => 'This user account is inactive.',
4 => 'This user account has been suspended.',
5 => 'This user account has been banned from logging in.'
];
// Check if the users status_id is in the array of statuses we made
// earlier. If it is, we will not let the user log in.
if (array_key_exists($user->status_id, $statuses)) {
// Log the user out.
$this->logout($request);
// Return them to the log in form.
return redirect()->back()
->withInput($request->only($this->loginUsername(), 'remember'))
->withErrors([
// This is where we are passing the message back.
$this->loginUsername() => $statuses[$user->status],
]);
}
// Make sure to include this for users with active accounts!
return redirect()->intended($this->redirectPath());
}
Also, notice the new addition at the end there (the return redirect()->intended($this->redirectPath());). This is so that users who have an active account can continue to their account.
@johnmkoster I guess I still don't understand how they are getting logged in if they have one of those errors because it says it's logging them out if that array key value matches a status. So shouldn't that be prevented before logging them in?
They get logged through some of the other stuff that goes on behind the scenes before authenticated gets called. It's just that using the authenticated method is a convenient place to put logic like this, and from the users point of view they were never logged in the first place.
Additionally, this lets us get away with not having to fetch the user ourselves (which Laravel s auth scaffolding will do anyway).
How can I quickly have that message be put within this:
<div class="form-group form-material floating {{ $errors->has('email') ? 'has-error' : '' }}">
<input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}"/>
<label for="email" class="floating-label">Email</label>
@if ($errors->has('email'))
<small class="help-block pull-left">{{ $errors->first('email') }}</small>
@endif
</div>
It should just work the way it is. When a user has an invalid account status, it is setting the error message to the username (email) field; that's where it will show up.
@johnmkoster I like it however the issue is that after I click submit it goes directly back to the / url.
The redirects are pretty easy to customize, and the docs for 5.2 are pretty straightforward for this:
@johnmkoster Don't mind if I hijack this thread for a very similar problem.
/**
* Override function
*
* Handle a login request to the application.
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|Redirect
*/
public function login(Request $request)
{
// if (User::where('email', $request->email)->first()->activated == 1) {
$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 ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$credentials = $this->credentials($request);
$checkActivated = User::where('email', $request->email)->first()->activated;
if ($checkActivated == 1) {
if ($this->guard()->attempt($credentials, $request->has('remember'))) {
return $this->sendLoginResponse($request);
}
} else {
}
// 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.
if (!$lockedOut) {
$this->incrementLoginAttempts($request);
}
return $this->sendFailedLoginResponse($request, $checkActivated);
}
/**
* Override function
*
* @param Request $request
* @param $checkActivated
* @return mixed
*/
protected function sendFailedLoginResponse(Request $request, $checkActivated)
{
switch ($checkActivated) {
case 0:
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([
$this->username() => 'You must activate your account before logging in.',
]);
break;
default:
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([
$this->username() => Lang::get('auth.failed'),
]);
break;
}
}
Is this a acceptable override method?
I am using Laravel 5.3 here with the Authentication Scaffolding. If possible, what is a better way you can suggest to handle such functions.
Thank you!
@ruchern Overriding the validateLogin method inside the login controller might be easier in your cause. Here is an example:
protected function validateLogin(Request $request)
{
$this->validate($request, [
'email' => 'required|exists:users,email,activated,1',
'password' => 'required',
]);
}
What it does is checks to make sure that a record exists for a user with the supplied email address and also checks to make sure that the activated column is set to 1.
Heres the doc links: https://www.laravel.com/docs/5.2/validation#rule-exists (5.2) https://www.laravel.com/docs/5.3/validation#rule-exists (5.3)
I got a cleaner way to do this with custom message.
Thank you for your help though.
protected function authenticated(Request $request, $user)
{
if ($user->activated == 0) {
$this->logout($request);
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([
$this->username() => 'You must activate your account to login.'
]);
} else {
return redirect()->intended($this->redirectPath());
}
}
Please or to participate in this conversation.