Laravel stuck on email/verify - laravel

I just applied the laravel email-verification and wanted to make sure my users are verified, before entering page behind the login.
I added the follwing code:
class User extends Authenticatable implements MustVerifyEmail
...
Auth::routes(['verify' => true]);
...
Route::get('management', function () {
// Only verified users may enter...
})->middleware('verified');
If a user registers he gets a note and an email to verify his mail. He clicks the button in the mail, gets verified and everything works perfectly well.
But I discovered another case:
If the user registers and won't verify his mail, he will always get redirected to email/verify.
For example if accidentally having entered a wrong email, he can't even visit the register page, because even on mypage.com/register he gets redirected to mypage.com/email/verify!
Is this done on purpose by Laravel? Did I miss something? Do I have to / is it possible to exclude the login/register pages from verification?
Thank you in advance

I have this issue before, I have this way to resolve that, if you want to customize it you can consider this way.
In LoginController.php you can add this a little bit code, I overwriting the default login method:
public function login(Request $request)
{
$this->validateLogin($request);
$user = User::where($this->username(), $request->{$this->username()})->first();
// 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 (method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($user->hasVerifiedEmail()) {
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 overwrite and add a new parameter to the sendFailedLoginResponse too to let the method know when to redirect to email/verify page or just add else in $user->hasVerifiedEmail() if block to redirect him to email/verify page
EDIT:
You can delete $this->middleware('guest') in LoginController and RegisterController to make logged in user can go to register and login page, but it will be weird if someone who already logged in can login or register again.

I had the same problem and I solved it very user friendly... (I think!)
First: Inside View/Auth/verify.blade.php put a link to the new route that will clear the cookie:
My mail was wrong, I want to try another one
Second: On your routes/web.php file add a route that will clear the session cookie:
// Clear session exception
Route::get('/clear-session', function(){
Cookie::queue(Cookie::forget(strtolower(config('app.name')) . '_session'));
return redirect('/');
});
This will clear the cookie if the user press the button, and redirect to home page.
If this doesn't work, just make sure that the cookie name you are trying to forget is correct. (Use your chrome console to inspect: Application -> cookies)
For example:
Cookie::queue(Cookie::forget('myapp_session'));

Related

How to validate routes if a user is admin or not?

//This is the middle ware
public function handle($request, Closure $next)
{
if(auth()->user()->isAdmin()) //isAdmin is a function in the User model which checks if the user is admin or not
{
return redirect('/admin');
} else {
return redirect('/home');
}
return $next($request);
}
//I already registered this middleware in kernel as well as verifyUser
Route::middleware(['auth', 'verifyUser'])->group(function() {
Route::get('/home', 'HomeController#index')->name('home');
Route::get('/admin', 'AdminController#index')->name('admin');
Route::get('/users/profile', 'UserController#view')->name('users.view-profile');
Route::get('/users/edit_profile', 'UserController#edit')->name('users.edit-profile');
});
Th main problem here is it shows this error in the browser
The page isn’t redirecting properly
Firefox has detected that the server is redirecting the request for this address in a way that will never complete.
This problem can sometimes be caused by disabling or refusing to accept cookies.
You're telling Laravel to redirect admins to /admin, and non-admins to /home.
However, you've made /admin and /home subject to that middleware, too, so when the user gets to /home it redirect them to /home again (and again, and again, and again, forever).
You likely need two changes:
A new middleware, applied only to admin routes, that only redirects non-admins away from those routes.
Put your home/admin logic as a one-off post-login step instead of on every pageview. See the path customization section of the Authentication docs.

How to hide login form after reaching the total of failed login attempts?

I want to hide the login form and display an error message instead, but I can't.
I tried to put the code below that rewrites the action on the controller that shows the form, but the method that checks for too many login attempts doesn't seem to work and never returns true.
public function showLoginForm(Request $request)
{
if (method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request) ) {
$seconds = $this->limiter()->availableIn($this->throttleKey($request));
return view('auth.block', array(
'seconds' => $seconds
));
}
return view('auth.login');
}
I managed the authentication process with php artisan make: auth login controller is the default generated by Laravel, the only change is in the action that displays the form.
The function hasTooManyLoginAttempts() needs, in the $request, the username (usually the email) as a key to know if the user has reached his max login attempts.
If, in the $request, there is not the username with a value the function is unable to verify the user login attempts.
So you cannot really know who is the user that wants to get your login form, you know who is only after he submitted the form.
IMHO the only way could be to add a username parameter to the GET request but you shoud provide it with some workarounds: cookies, session etc.
Looking at Laravel's code, it checks for hasTooManyLoginAttempts based on throttleKey and maxAttempts.
The throttleKey is dependent on the user's email and IP address. So the output of the following code is something like: info#example.com|127.0.0.1 and that is your throttleKey.
protected function throttleKey(Request $request)
{
return Str::lower($request->input($this->username())).'|'.$request->ip();
}
Now Laravel gets the user's email (username) from $request->input($this->username()) when you send a POST request, which you don't have access to in the showLoginForm method because it's called on the GET request.
Anyway, if you want to block the login form you'll need to come up with your own unique throttleKey and then override the method. Say you want your throttleKey to be based only on the IP address - which is not recommended. Here's how you do it:
// In LoginController.php
protected function throttleKey(Request $request)
{
return $request->ip();
}

php laravel preventing multiple logins of a user from different devices/browser tabs at a given time

Does laravel provide a way to prevent multiple logins of a user from different devices / browsers at a given time? If yes then how can i force a user to logged in from a single device at a single time. I am developing a online quiz app using laravel 5.6 where users can logged in from a single place and take test.
laravel provide this method to invalidating and "logging out" a user's sessions that are active on other devices logoutOtherDevices()
to work with this method you need also to make sure that the
Illuminate\Session\Middleware\AuthenticateSession
middleware is present and un-commented in your app/Http/Kernel.php class' web middleware group:
'web' => [
// ...
\Illuminate\Session\Middleware\AuthenticateSession::class,
// ...
],
then you can use it like this
use Illuminate\Support\Facades\Auth;
Auth::logoutOtherDevices($password);
Perhaps this should get you started:
Add a column in users_table.php
$table->boolean('is_logged_in')->default(false);
When a user logs in: LoginController.php
public function postLogin()
{
// your validation
// authentication check
// if authenticated, update the is_logged_in attribute to true in users table
auth()->user()->update(['is_logged_in' => true]);
// redirect...
}
Now, whenever a user tries to login from another browser or device, it should check if that user is already logged in. So, again in LoginController.php
public function index()
{
if (auth()->check() && auth()->user()->is_logged_in == true) {
// your error logic or redirect
}
return view('path.to.login');
}
When a user logs out: LogoutController.php
public function logout()
{
auth()->user()->update(['is_logged_in' => false]);
auth()->logout();
// redirect to login page...
}

Laravel 5.5 restrict duplicate login

I have overwritten Login and Logout functionality as I need to check many more conditions to authenticate the user like below.
public function login(Request $request)
{
$this->validateLogin($request);
$input=$request->all();
$user=User::where('username',$input['username'])->first();
//If Temp Password is set
if(strlen($user->temp_password)>10)
{
if (Hash::check($input['password'], $user->temp_password))
{
Auth::login($user);
$this->setUserSession($user);
$landing_page=Menu::find($user->landing_page);
return redirect()->route($landing_page->href);
}
else {
session()->put('failure','Invalid Username or Password');
return redirect('/login');
}
}
else{ //If Temp password is not set
if (Hash::check($input['password'], $user->password))
{
Auth::login($user);
$this->setUserSession($user);
$landing_page=Menu::find($user->landing_page);
return redirect()->route($landing_page->href);
}
else {
session()->put('failure','Invalid Username or Password');
return redirect('/login');
}
}
}
Now I need to restrict Same user from login once again in some other screen or place. I have checked Session Data but nothing is stored as Unique for a User.
ie. If a username admin is loged in US the same username admin must not be allowed to login from UK.
Update
Oh bagga, question wasn't quite clear. You are trying to restrict the number of sessions to 1 only. If I get it, then you will have to use a database session driver. Right now, I think you may be using the default driver (file). It only checks the session within the same browser. Using database session may allow you to check for session everywhere, and restrict the number of connections.
First, make sure your routes are within the web middleware so they can access sessions. Then, inside of the web middleware, create a group of routes that are only accessible for users who are not logged in.
Route::group(['middleware' => 'guest'], function () {
Route::get('login', 'LoginController#login');
// any other route
});
Logged in users won't be able to access the login route anymore.
You could also do the check in your login function to see if the user's is already connected by using
if (Auth::check()) {
// user is connected
// redirect them
}
What does this->setUserSession($user) do?
You can do this using login token.
Generate a login token and keep it in database.
And check for it's entry in database while logging in.
If it doesn't exist let log in success.
Else fail.
And delete login token every time user logs out.
Or
you can generate new token on each login success. And deleting old token and invalidating the old login.
But in this case you have to keep that token in session and for each request you have to check that token with database token.
If it matches, allow user
Else logout the user with notice.
I'll prefer the second method personally.
As you can check for the token in the middleware itself.

How to forward argument for redirect::route->with when hitting an auth filter using Laravel?

I have the following problem:
After a user is created and the user has confirmed her email.
I would like the user to login,
I use the following code to do that:
Redirect::route('login-forward')->with('alert_message', ...)
the alert messages simply states that everything went well, and I would like her to login.
login-forward is protected with an before filter, that runs the actual login form,
When the user then logs in sucesfully, she is brought back to login-forward, whic then puts the user at her personal landing page.
the code for the route 'login-forward is:
Route::get('my-login', array(
'as' => 'login-forward',
'before' => 'auth',
function ()
{
$user = Auth::user();
switch ($user->role)
{
case 'Administrator':
return Redirect::route('admin_dashboard');
case 'FreeUser':
return Redirect::route('cs-dashboard');
default:
return Redirect::route('/');
}}));
the problem is, the ->with('alert_message',...) is not seen by the real login route called by the before filter.
How do I solve this?
The best approach is to let the user logs in automatically when the email is confirmed, if the user confirms the account creation then when you find that user is valid then you may log in that user using something like:
// $user = Get the user object
Auth::login($user);
Also you may use:
Session::put('alert_message', ...);
Redirect::route('login-forward');
Then when the user logs in for the first time, just get the message from Session using:
// Get and show the alert_message
// from session and forget it
#if (Session::has('alert_message'))
{{ Session::pull('alert_message') }}
#endif
So, when you pull the message from the Session to show it, the message will no longer be available in the Session or you may use Session::reflash() to flash the flashed session data for another subsequent request until you get in the page where you want to show the message.
The best choice is - you can make forward to login form without redirect from method of controller for url of personal page.
return Route::dispatch(Request::create('login'))->getOriginalContent();

Resources