Invalidating Session not logging out - Laravel 5 - php

So basically a user can stay logged in accross iPhone, Chrome, Firefox and multiple browsers and I want to stop that, and only allow the user to be logged in at (1 session only at a time).
Meaning:
When the user logs in somehwere else... it logs them out on the other.
I have added the following to the very bottom of my LoginController.php
/**
* The user has been authenticated.
*
*
* #return mixed
*/
protected function authenticated()
{
\Auth::logoutOtherDevices(request($password));
}
I also uncommented the line: \Illuminate\Session\Middleware\AuthenticateSession::class, in my Kernel.php
But it still allows the user to stay logged in across many browsers.
I would like it to invalidate the session correctly and log the user OUT everywhere else wherever it is logged in.
Here is my complete LoginController.php just incase I have made some mistake:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
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 = '/dashboard';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
$this->middleware('guest:admin', ['except' => 'logout']);
}
public function username()
{
return 'username';
}
public function logoutUser()
{
$this->guard()->logout();
$this->session()->invalidate();
return redirect('/login');
}
}
/**
* The user has been authenticated.
*
*
* #return mixed
*/
protected function authenticated()
{
\Auth::logoutOtherDevices(request($password));
}

SO, basically this is a custom logoutotherdevices which I have made with the help of middleware.
Basically the idea is to pass active_key value as random string and keep it in user table for that user as well as store it in session, we need to create new value at time of registration,update the column value for every user login and set it to null on logout, so if in not matches with the value stored in session, we would logout the user from that session.
Step 1:
Create the table column which stores random string in user table.
For that I have added a new column in user table(App\User Model), named active_key.
$table->string('active_key')->nullable();
Added a column in migration. Then migrate it using php artisan migrate.
NOTE: If you don't want to create a new column you use the remember_token column key though I will suggest not to use it,as it is originally used for some other purpose.
IMP : At the end, add active_key to $fillable array in User Model
Step 2:
Note in your Auth\LoginController, we would override the authenticated and logout method,
Just as the class Auth\LoginController begins, we can see trait included as use AuthenticatesUsers, I have changed it to
use AuthenticatesUsers{
logout as logoutFromAuthenticatesUsers;
}
so to change the default logout() function name defined in trait AuthenticatesUsers to logoutFromAuthenticatesUsers() and then I have used it in logout function as seen below.
protected function authenticated(Request $request, $user)
{
$this->generateAndUpdateActiveKey($request, $user);
return redirect()->intended($this->redirectPath());
}
public function logout(Request $request)
{
$user = \Auth::user();
if(!$user->update(['active_key' => null])){
throw new \Illuminate\Database\Eloquent\ModelNotFoundException();
}
if($request->session()->has('active_key')){
$request->session()->forget('active_key');
}
$this->logoutFromAuthenticatesUsers($request);
return redirect()->intended($this->redirectPath());
}
What it will do is authenticated method will be called after the user is authenticated successfully, then the generateAndUpdateActiveKey() will update the active_key column in the user table and store the same value in session.
generateAndUpdateActiveKey() is defined in App\Http\Controller to avoid redundancy.
public function generateAndUpdateActiveKey(\Illuminate\Http\Request $request, \App\User $user){
$activeKey = substr(md5(time().\Auth::user()->name),0,16);
$request->session()->put('active_key', $activeKey);
// $request->session()->put('loggedin_ip',$request->ip());
if(!$user->update(['active_key' => $activeKey])){
throw new \Illuminate\Database\Eloquent\ModelNotFoundException();
}
}
In case of logout, we override it from the trait AuthenticatesUsers used to delete the active_key value for the login user from the users table and then remove it from session then call the logout function.
To create new active_key value when a user registers
For that I have overridden the registered() method from trait RegistersUsers( added as use RegistersUsers;)
So in, Auth\RegisterController.php
protected function registered(Request $request, $user)
{
$this->generateAndUpdateActiveKey($request, $user); // same method defined in controller
return redirect()->intended($this->redirectPath());
}
Step 3:
With all set up we now just need to check that every time the page is refreshed, the current session is the only session for that user.
For that we use a middleware, I have named it ActiveLogin.
php artisan make:middleware ActiveLogin will create a basic layout of middleware in App\Http\Middleware -> ActiveLogin.php.
Register the middleware in App\Http\kernel.php, I have added it as route middleware, you can even add it along the group of web middleware to check as group for each routes.
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
.....
'activelogin' => \App\Http\Middleware\ActiveLogin::class,
];
We will change the ActiveLogin Middleware as
<?php
namespace App\Http\Middleware;
use Closure;
class ActiveLogin
{
/**
* Handle an incoming request to check the current user is logged in current device.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ( \Auth::check() ){
if ($request->session()->has('active_key')){
if(\Auth::user()->active_key != null && strcmp(\Auth::user()->active_key, $request->session()->get('active_key')) !== 0){
\Auth::logout();
return redirect()->to('/')->with(['warning' => 'Your session has expired because your account is logged in somewhere else.']);
}
}
}
return $next($request);
}
}
Then you can use it in your routes web.php
Route::group(['middleware' => ['activelogin']], function(){
Route::get('/', function () {
return view('welcome');
});
Route::get('/home', 'HomeController#index')->name('home');
});
You can show your warning in welcome.blade.php as
#if(session()->has('warning'))
<div class="alert alert-warning" role="alert">
{{ Session::get('warning') }}
</div>
#endif
Note: If you add it in route group of web then it will be applied automatically to all the routes in web.php .
Demo
This is the first login of user try2 from separate session(incognito mode),
This is the second login of user try2 from separate session(normal mode),
Once the same user logs in from separate session(normal mode), if user tries to go to some other link or refeshes page from the first session(in incognito), user is logged out from first session with warning message as shown in welcome page picture below

Related

Laravel 8 + Jetsream: How to Redirect to Prior Page After Login?

I have a basic web page where I want the user to be able to click the login link, go through the login, and then be returned to that page (not the home page). The page has some features that can only be seen when the user is logged in.
I am having trouble with this, no matter what when I go to login it returns to the home page after authentication or whatever I have set as the constant, not the prior page.
Fortify.php has a home path that is a constant, so I can't update that with an expression either...
'home' => RouteServiceProvider::HOME,
Here is the middleware RedirectIfAuthenticated.php, it is the standard Laravel and I'm wondering what would need to be updated.
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null ...$guards
* #return mixed
*/
public function handle(Request $request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}
I should note too, that if I add a middleware route to the page, as in the below example, then the process works correctly as far as returning the user back to the prior page.
Route::middleware(['auth:sanctum', 'verified'])->get('/agenda', function () {
return view('agenda');
})->name('agenda');
However, I need the user to be able to view the agenda page, even if they are a guest... but, once logged in, they will be returned to the agenda page which will have some additional features. I can't seem to find in the documentation anything about this, unfortunately.
In AuthenticatesUsers.php
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();
$this->clearLoginAttempts($request);
if ($response = $this->authenticated($request, $this->guard()->user())) {
return $response;
}
return $request->wantsJson()
? new JsonResponse([], 204)
: redirect()->back();
}
Or You Can Do this in Your Default Login Controller in Line 31
protected $redirectTo = "/your-path";
When the auth middleware detects an unauthenticated user, it will redirect the user to the login named route. You may modify this behavior by updating the redirectTo function in your application's app/Http/Middleware/Authenticate.php file
https://laravel.com/docs/8.x/authentication#redirecting-unauthenticated-users

Laravel, first user is only user

I am building a Laravel site for personal use and I would like to make the first user to register on the site be the only user. So with a fresh Laravel install with the ui installed, migration sorted and no users registered, I would like the register route to be reachable. But if there is a registered user, block the register route and only allow the login route to be reachable.
I could do something like this in the web.php
Route:get('/register', function () {...})->auth();
But I would have to do that after I first create a user. I'd rather do it in a more controllable fashion.
Edit
I don't doubt that #yves-kipondo's answer is the more correct option if I were to create this for someone else.
The solution I went with is a simple one. In my register controller I just add a check in the constructor, if there already is a user return a 404.
public function __construct() {
if (!User::all()) {
$this->middleware('guest');
} else {
abort(404);
}
}
You can create a Middleware which will be register on the register route
<?php
namespace App\Http\Middleware;
use Closure;
class RegisterOnce
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (User::count() !== 0) {
// you can redirect wherever you want
return redirect('home');
}
return $next($request);
}
}
After that you can register the middleware in the app/Http/Kernel.php by adding this line after all registered routes middleware like this
protected $routeMiddleware = [
\\ ... previous registered middleware
'once' => App\Http\Middleware\RegisterOnce::class,
];
and you can customize the register route like this in the routes/web.php file
which wille replace the default set by Auth::routes();
Route::get('register', [App\Controllers\Auth\RegisterController::class, 'showRegistrationForm'])
->name('register')
->middleware('once');

Using custom authentication on Laravel 6

I would like to manually authenticate the users in my company. The issue is that, I have 2 tables, called Student and Staff in the Oracle database.
As for the Student table, I get the idea of overriding the built in Auth method provided through the auth scaffolding command as the username and password are stored right into the table.
As for the Staff table, the password is stored a different column/table and encrypted using a stored procedure/package so the only way to get the user validation is by calling the package which only returns 0 or 1 only.
What I have done,
I wrote my own Routes, and added my own functions in LoginController.
public function loginStaff(Request $req){
$username = Str::upper($req->input('username'));
$password = $req->input('password');
$users = PortalUser::where('ID', $username)->firstOrFail();
if ($users->user_type == 'STAFF'){
$queryResult = DB::select('select PACKAGE.validStaff(?,?) from dual',[$username, $password]);
if($queryResult == 1){
//this is where I would like to auth the user.
//using Auth::Attempt and Auth::Login will only run the default query
}
}
I have successfully returned value of 1 and 0 in the controller.
So is there anything that I am missing?
Or should I manually set the session by myself using the session() method?
Thank you.
If you want to manually authenticate users, you can easily use sessions. Have the following code as reference:
//this is where I would like to auth the user.
//using Auth::Attempt and Auth::Login will only run the default query
// typically you store it using the user ID, but you can modify the session using other values.
session()->put('user_id', user id from database here);
And if you want to check whether user is authenticated, modify RedirectIfAuthenticated middleware to this:
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (session()->has('user_id')) {
return redirect( custom path here );
}
return $next($request);
}
}
When you want to logout the user, simply destroy the session key
session()->forget('user_id');
**Note: ** many broadcasting and addons use Laravel's authentication system (Guards) and you may need to hook into their code if you want to use them with your custom auth system
Laravel provides Custom Session Drivers which you can use to create or delete your sessions
<?php
namespace App\Extensions;
class MongoSessionHandler implements \SessionHandlerInterface
{
public function open($savePath, $sessionName) {}
public function close() {}
public function read($sessionId) {}
public function write($sessionId, $data) {}
public function destroy($sessionId) {}
public function gc($lifetime) {}
}
Hope it helps, if not then comment down below. Will help you out.
###### Update #######
I think then you do have to make custom HTTP sessions from Laravel
Step 1: Create another table in your database for session, like this;
Schema::create('sessions', function ($table) {
$table->string('id')->unique();
$table->unsignedInteger('user_id')->nullable();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity');
});
Step 2: Store data in the session, you will typically use the put method or the session helper;
// Via a request instance...
$request->session()->put('key', 'value');
// Via the global helper...
session(['key' => 'value']);
Step 3: Get the key for specific user when your function returns 1
$value = $request->session()->get('key', function () {
return 'default';
});
Step 4: Delete the session, after some time you need to delete the session for security reasons then you can do.
$value = $request->session()->pull('key', 'default');

Avoid "auto login" when password is reset

I am working on a Laravel project. I was not the original developer, so if I make a mistake, forgive me and explain what it was please.
In this project, we have a module to request a new password. when the form is submited, the user is redirected to route('password.request') which is, I believe, somewhere hidden in the framework.
The problem is that when the user gets it's new password, he is automaticaly logged in and can access the pages. But he is not supposed to because he does not have admin rights.
So I tried to logout and redirect the user to the main page, without any luck.
Can someone explain why is laravel (or "me", as they are some parts of the project I have not explored yet) doing that and how to fix this?
reset.blade.php (form to request new password)
form class="form-horizontal" role="form" method="POST" action="{{ route('password.request') }}">
{{ csrf_field() }}
<input type="hidden" name="token" value="{{ $token }}">
....
my custom logout route:
Route::get('/customLogout', 'Auth\LoginController#customLogout');
resetPasswordController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* #var string
*/
protected $redirectTo = '/customLogout';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
}
}
method in LoginController.php
public function customLogout(){
//Session::flush();
return redirect()->route('/');
}
Ok, I (finnaly) found a way.
I overrided the function called "resetPassword" and deleted the login piece of code.
This function comes from the framework (can't remember the file, if someone could help on that :S )
I overrided the function in my ResetPasswordController.php
protected function resetPassword($user, $password)
{
$user->forceFill([
'password' => bcrypt($password),
'remember_token' => Str::random(60),
])->save();
//$this->guard()->login($user);
}
This make my password changing and redirect automaticaly to the main page.
Edit: Oh, and don't forget to add this in your includes:
use Illuminate\Support\Str;
Go to auth/ResetPasswordController
and override this method resetPassword method and make it like this
/**
* Reset the given user's password.
*
* #param \Illuminate\Contracts\Auth\CanResetPassword $user
* #param string $password
* #return void
*/
protected function resetPassword($user, $password)
{
$this->setUserPassword($user, $password);
$user->save();
event(new PasswordReset($user));
}
at the top of controller add this line to avoid any error
use Illuminate\Auth\Events\PasswordReset;

Redirect after register in Laravel

I am using Laravel 5.
I wanted to redirect a user after a successful registration. I have tried these in my authcontroller but it doesn't work.
protected $loginPath = '/plan';
protected $redirectTo = '/plan';
protected $redirectAfterRegister = 'plan/';
These work on successful login though but not after registration.
I have also tried postRegister to render a view but using a postRegister method overrides the registration process and I don't want to do that. I just want to redirect the user to a page on successful registration.
There are two options to specify where to redirect the user in the
app/Http/Controllers/Auth/RegisterController.php
For a simple URL you can override this property.
protected $redirectTo = '/home';
If you have a more complicated logic than just one static URL, since Laravel 5.3 you can add a method in the same class RegisterController, with name redirectTo():
protected function redirectTo()
{
if (auth()->user()->role_id == 1) {
return '/admin';
}
return '/home';
}
The method behavior will override $redirectTo property value, even if the value is present.
Overriding the postRegister function like you mention should work, you would do this in your AuthController:
public function postRegister(Request $request)
{
$validator = $this->registrar->validator($request->all());
if ($validator->fails())
{
$this->throwValidationException(
$request, $validator
);
}
$this->auth->login($this->registrar->create($request->all()));
// Now you can redirect!
return redirect('/plan');
}
Or something like it. Copy it from the AuthenticatesAndRegistersUsers that is used in the top of your AuthController, here you will find all the functions
For this to work your AuthController should use the 'AuthenticatesAndRegistersUsers' trait, which is there by default.
More info about redirects in case you want to redirect to a named route: http://laravel.com/docs/5.0/responses#redirects
Simply add below line to AuthController class in Auth/AuthController.php
protected $redirectPath= '/plan';
Above redirect path will be used for successful login and successful register.
You can also modify the return of register() in RegisterUsers.php:
public function register(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
Auth::guard($this->getGuard())->login($this->create($request->all()));
// Originally the parameter is $this->redirectPath()
return redirect()->to('/plans');
}
Here is laravel 5.4 solution
/**
* The user has been registered.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function registered(Request $request, $user)
{
//User register now here you can run any code you want
if (\Request::ajax()){
return response()->json([
'auth' => auth()->check(),
'intended' => $this->redirectPath(),
]);
exit();
}
return redirect($this->redirectPath());
}
keep in mind register() Handle a registration request for the application.
where registered() called when user created.
after run command
php artisan make:auth
laravel create auth controller and resource files for change the route of register user
just go to below path
App\Http\Controllers\Auth\RegisterController
and change the protected $redirectTo param
and you can user this way in LoginController next of RegisterController
for redirect after login

Categories