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

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...
}

Related

Laravel multiple authentication from two different route and view

I want to implement a system where 6 types of users exist. So one is 'customer' who will login by a route like /login and rest of 5 users are admins and only they will be login using another route /system/base-admin. However, 'customer' never login with the /system/base-admin route if anyhow can known this route. And both route have different login form and if they failed to login 'customer' will be redirected /login and admins /system/base-admin.
I know about $guard and middleware check.
Question: How can i implement above scenario and how react professionals with this scenario?
Route::get('/login','CustomerLoginController#processLogin')->name('customer.login');
Route::get('/system/base-admin', 'AdminLoginController#processAdminLogin')->name('system.admin')
My Controller Looks like
public function processLogin(){ return view('customer.login');}
public function processAdminLogin(){ return view('admin.login')}
Thank you in advance.
The only reason I see to have different endpoints for login is to have different views.
Copy your Auth\LoginController, change $redirectTo to redirect to your admin panel. Overwrite AuthenticatesUsers\showLoginForm to show your admin form and update middleware in __construct.
Protect all your admin routes with admin middleware.
Now. Your users CAN login to your panel. BUT nothing will happen since they don't have access.
If you want to show them some kind of message when they try you can overwrite AuthenticatesUsers\login method with something like this
...
if ($this->attemptLogin($request)) {
if(!auth()->user()->isAdmin()){
throw ValidationException::withMessages([
$this->username() => 'You don\'t have access to this page',
]);
}
return $this->sendLoginResponse($request);
}
...

Laravel stuck on email/verify

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'));

how to check if user is authenticated with passport (get user from token using laravel-passport)

I am using Passport to log in users to a Laravel API endpoint, users get authenticated using their social accounts (google, facebook) using laravel-socialite package.
the workflow of logging users in and out works perfectly (generating tokens...Etc). The problem is I have a controller that should return data based on whether there is a user logged in or not.
I do intercept the Bearer token from the HTTP request but I couldn't get the user using the token (I would use DB facade to select the user based on the token but I am actually looking whether there is a more clean way already implemented in Passport)
I also don't want to use auth:api middleware as the controller should work and return data even if no user is logged in.
this is the api route:
Route::get("/articles/{tag?}", "ArticleController#get_tagged");
this is the logic I want the controller to have
public function get_tagged($tag = "", Request $request)
{
if ($request->header("Authorization"))
// return data related to the user
else
// return general data
}
Assuming that you set your api guard to passport, you can simply call if (Auth::guard('api')->check()) to check for an authenticated user:
public function get_tagged($tag = "", Request $request)
{
if (Auth::guard('api')->check()) {
// Here you have access to $request->user() method that
// contains the model of the currently authenticated user.
//
// Note that this method should only work if you call it
// after an Auth::check(), because the user is set in the
// request object by the auth component after a successful
// authentication check/retrival
return response()->json($request->user());
}
// alternative method
if (($user = Auth::user()) !== null) {
// Here you have your authenticated user model
return response()->json($user);
}
// return general data
return response('Unauthenticated user');
}
This would trigger the Laravel authentication checks in the same way as auth:api guard, but won't redirect the user away. In fact, the redirection is done by the Authenticate middleware (stored in vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php) upon the failure of the authentication checking.
Beware that if you don't specify the guard to use, Laravel will use the default guard setting in the config/auth.php file (usually set to web on a fresh Laravel installation).
If you prefer to stick with the Auth facade/class you can as well use Auth::guard('api')->user() instead or the request object.
thanks to #mdexp answer
In my case I can resolve my problem with using
if (Auth::guard('api')->check()) {
$user = Auth::guard('api')->user();
}
In my controller.

How can I redirect to step2 page after verify the email based on their role?

How can I redirect to a step2 page after verifying the email based on their role?
Because I have two user types, both after register their email and pass, they will get a verification email, after that clicking button it redirects to home instead it will go to /signup/employee/step2 or /signup/user/step2.
Can I ask some tips on how to redirect the user based on their role with their step2 to complete registration?
Well, its obvious you'll need some type of ACL (access-control). If your app, need just that specific part, you can add a new column in your user table, is_employee and make it bool.
After that just implement a new middleware.
However, I would personally go with https://github.com/spatie/laravel-permission it is way easier :)
Laravel exposes an Event when user verify his account, you can add a custom listener to it and redirect user where you want.
You need to do something like this:
// app/Providers/EventServiceProvider.php
protected $listen = [
'Illuminate\Auth\Events\Verified' => [ // Laravel event
'App\Listeners\RedirectUsers', // Your custom listener
],
];
run # php artisan make:listener RedirectUsers -e Illuminate\Auth\Events\Verified
// app/Listeners/RedirectUsers.php
public function handle(OrderShipped $event)
{
if(Auth::user()->hasRole('admin')) {
return redirect('/admin');
}
// ...
}

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.

Resources