Laravel authenticated user logout error - laravel

tried to implement a simple user register/login function on my site using the laravel default controllers (auth/password), but as soon as I login, the class RedirectIfAuthenticated handle function prevents all access to auth url's, thus I cannot logout anymore. Is there a bug and I need to write an exception on the handle function or have I missed something?
Here is how the class looks like by default:
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)
{
//dd($next($request));
if (Auth::guard($guard)->check()) {
return redirect('/articles');
}
return $next($request);
}
}

The AuthController's constructor should look similar to this:
public function __construct()
{
$this->middleware('guest', ['except' => 'logout']);
}
The guest middleware is handled by the RedirectIfAuthenticated class and, in order to have the logout functionality working, you should choose one:
call the logout method from your AuthController.
call whichever method you use for logout and exclude it in AuthController's constructor:
public function __construct()
{
$this->middleware('guest', ['except' => '<whichever_method>']);
}

For potentially more-advanced reasons and needs, I will show a different idea.
Inside any middleware, a person could implement their own except list. Here is a reference:
<?php
namespace App\Http\Middleware;
use Closure;
class CustomThing
protected $except = [
'api/logout',
'api/refresh',
];
public function handle($request, Closure $next)
{
foreach ($this->except as $excluded_route) {
if ($request->path() === $excluded_route) {
\Log::debug("Skipping $excluded_route in this middleware...");
return $next($request);
}
}
\Log::debug('Doing middleware stuff... '. $request->url());
}
}
I will leave it up to imagination to extend that to support other types of URLs. For example, investigate matchers such as $request->url(), $request->fullUrl(), and $request->is('admin/*').
A person could investigate the vendor code for the VerifyCsrfToken middleware and make their custom one support something like this:
protected $except = [
'api/logout',
'api/refresh',
'foo/*',
'http://www.external.com/links',
];
If you want it to be a reuseable solution, make that matching algorithm a Trait and import it into any middleware you want to exclude routes from.

Related

Prevent login to user and custom guards at the same time

I am using a custom guard for a different type of user using a custom guard labelled business_user.
I have noticed I am able to login to as both normal users (web) and my business_users.
I've read in the Pusher documentation that I used to create my custom guards in the first place to add additional middleware into my "LoginController".
But I don't actually even have a LoginController, I've created my own controllers for each user type. AuthController (for web) and BusinessController (for business_user).
I have created a third controller labelled LoginController with the following code:
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = '/dashboard';
public function __construct()
{
$this->middleware('guest')->except('logout');
$this->middleware('guest:business_user')->except('logout');
}
}
I also updated my RedirectIfAuthenticated as follows:
class RedirectIfAuthenticated
{
public function handle($request, Closure $next, $guard = null)
{
if ($guard == "business_user" && Auth::guard($guard)->check()) {
return redirect('/dashboard');
}
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
I also have a RedirectIfAuthenticated middleware inside my Middleware folder.
<?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 ($guard == "business_user" && Auth::guard($guard)->check()) {
return redirect('/dashboard');
}
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
}
When I land on my user login page, it still allows me to attempt logging in. Can someone tell me how to resolve this?
In LoginController, you can override authenticated method.
/**
* The user has been authenticated.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function authenticated(Request $request, $user)
{
auth()->login($user); // this method will login with default guard
return redirect()->intended($this->redirectPath());
}
I think because the order of middleware
<?php
public function __construct()
{
$this->middleware('guest')->except('logout'); // this procress first and redirect to login page
$this->middleware('guest:business_user')->except('logout');
}
So, I think you can check directly in __construct() of LoginController or in login view (blade file)
#if (Auth::check('business_user'))
You are already logged in (or perform a redirect somewhere)
#else
//display login form
#endif

Laravel group multiple Middleware

In my application I have three user roles:
user
editor
admin
When editor logs into the admin section, some of the sections are hidden (users manage, system information etc.) and of course, the admin can see everything.
So, for this purpose I've created two middleware: Admin and Editor. This is the code.
Admin middleware.
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class Admin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::check()) {
if(Auth::user()->role_id == 3) {
return $next($request);
}
}
return redirect('/');
}
}
Editor middleware:
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class Editor
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::check()) {
if(Auth::user()->role_id == 2) {
return $next($request);
}
}
return redirect('/');
}
}
Here's the part of the Kernel:
protected $routeMiddleware = [
'admin' => \App\Http\Middleware\Admin::class,
'editor' => \App\Http\Middleware\Editor::class,
];
Now I'm trying to build the routes that will be available to those user roles.
If I do it only for the admin or the editor, it works fine, but when I combine them, the one user can login and the other cannot.
Here's the code only for the admin and it works fine.
Route::middleware('admin')->group(function(){
Route::get('/admin', 'PagesController#adminIndex');
Route::resource('/admin/pages', 'PagesController');
Route::resource('/admin/news', 'NewsController');
Route::resource('/admin/users', 'UsersController');
...
});
I've tried to combine them with this code, but it's not working (cannot login into the admin section at all):
Route::middleware(['admin', 'editor'])->group(function(){
Route::get('/admin', 'PagesController#adminIndex');
Route::resource('/admin/pages', 'PagesController');
Route::resource('/admin/news', 'NewsController');
Route::resource('/admin/users', 'UsersController');
...
});
How can I solve this problem?
P.S. Later I want to build a logic for the User role too, so there's must a way to combine the routes.
You can solve the problem with help of Middleware Parameters and instead of several middlewares for each role use only one universal middleware with roles as parameters.
For example:
protected $routeMiddleware = [
'checkRole' => \App\Http\Middleware\CheckRole::class,
];
Middleware:
<?php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class CheckRole
{
public function handle($request, Closure $next, ...$roles)
{
$roleIds = ['user' => 1, 'editor' => 2, 'admin' => 3];
$allowedRoleIds = [];
foreach ($roles as $role)
{
if(isset($roleIds[$role]))
{
$allowedRoleIds[] = $roleIds[$role];
}
}
$allowedRoleIds = array_unique($allowedRoleIds);
if(Auth::check()) {
if(in_array(Auth::user()->role_id, $allowedRoleIds)) {
return $next($request);
}
}
return redirect('/');
}
}
Routes:
Route::middleware(['checkRole:admin,editor'])->group(function(){
//Your routes
});
It should be like below.
Route::middleware(['auth'])->group(function(){
//common routes will goes here
Route::middleware(['admin'])->group(function(){//admin routes will goes here
Route::get('/admin', 'PagesController#adminIndex');
Route::resource('/admin/pages', 'PagesController');
Route::resource('/admin/news', 'NewsController');
Route::resource('/admin/users', 'UsersController');
});
Route::middleware(['editor'])->group(function(){
//editor routes goes here.
});
});
The problem is that your middleware(['admin', 'editor']) is checking the both roles i.e. admin,editor for user and you have only one role for user. That is reason why it is not working
There are great packages out there for managing the user roles which are easy to use . I suggest you to use Spatie Laravel Permission if you want tutorials on it watch Bitfumes Video

laravel auth middleware not redirectly on intended page

I am using hesto/multi-auth package
as default if i have assigned the auth middleware to a route the so after login it should redirect me back to the intended page but it's doing only the first time..
everything working exactly i want only the first time but once i logout and try to access the route again it does go to login page and than redirects to the user/home, but first time it works perfect see the 40 sec video
http://neelnetworks.org/video/laravel.mp4
any solution for this?
these are my web routes
Route::get('/', 'PagesController#getIndex')->middleware('user');
Route::group(['prefix' => 'user'], function () {
Route::get('/login', 'UserAuth\LoginController#showLoginForm');
Route::post('/login', 'UserAuth\LoginController#login');
Route::post('/logout', 'UserAuth\LoginController#logout');
Route::get('/register', 'UserAuth\RegisterController#showRegistrationForm');
Route::post('/register', 'UserAuth\RegisterController#register');
Route::post('/password/email', 'UserAuth\ForgotPasswordController#sendResetLinkEmail');
Route::post('/password/reset', 'UserAuth\ResetPasswordController#reset');
Route::get('/password/reset', 'UserAuth\ForgotPasswordController#showLinkRequestForm');
Route::get('/password/reset/{token}', 'UserAuth\ResetPasswordController#showResetForm');
});
here is my Pages Controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PagesController extends Controller
{
public function getIndex()
{
return "hello";
}
}
first time it works perfectly why not after we logged in once?
it works again if i clear all my cache and cookies, is this a default behaviour or is this a bug in laravel? can you please clarify or is it a issue with the package
the issue has been raised in github https://github.com/Hesto/multi-auth/issues/46
Make your showLoginForm method like this inside your UserAuth/LoginController.php
public function showLoginForm()
{
session()->put('url.intended',url()->previous());
return view('user.auth.login');
}
Because it changes the previous url when posting form to /user/login and you will be redirected to /user/home if you logged in
after so much of digging i found out the correct solution
in RedirectIfNot{guard-name} eg RedirectIfNotAdmin
we need to add this line
session()->put('url.intended', url()->current());
so the middleware will look like this
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfNotAdmin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $guard = 'admin')
{
if (!Auth::guard($guard)->check()) {
session()->put('url.intended', url()->current());
return redirect('/admin/login');
}
return $next($request);
}
}
Default redirect for laravel after login is to go to /home set in the LoginController:
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/home';
and there is default middleware RedirectIfAuthenticated
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 (Auth::guard($guard)->check()) {
return redirect('/home');
}
return $next($request);
}
}
and in app/Http/Controllers/Auth/RegisterController.php
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after login / registration.
*
* #var string
*/
protected $redirectTo = '/home';
So that is where you need to make changes in order to work your way...

Write function in laravel which needs to execute before any controller

Hi i am doing a website in laravel.
I am trying to do like a function that needs to execute before any controller.
Example :
I have function like
function xyz(){
//do code here
}
This function need to execute when user on site by refreshing page or doing some ajax requst.
I am aware with the codeigniter there is a way to do this using hook
$hook['pre_controller'] = array(
'class' => 'MyClass',
'function' => 'Myfunction',
'filename' => 'Myclass.php',
'filepath' => 'hooks',
'params' => array('beer', 'wine', 'snacks')
);
What is the way in laravel to do this ?
You can use Laravel Middleware to achieve this. The middleware can be registered as global for all controllers / routes, and will let you execute that function (or you can register it for subset of routes by using router groups).
Example:
<?php
namespace App\Http\Middleware;
use Closure;
class MyMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// call your function
$this->xyz();
return $next($request);
}
public function xyz()
{
// do something
}
}
See the Laravel documentation on Middleware.
For that, you need to declare the function in the public construct part of your controller. For example, if you had a Controller for all pages as follows and you need to check if a user is above a certain age.
class PageController extends Controller
{
public function __construct()
{
Run this function for every function in controller
$this->middleware('age');
}
public function index()
{
//my public static page
}
}
Create a middleware with the artisan command
php artisan make:middleware AgeMiddleware
in your AgeMiddleware, you can have a function as follows.
class AgeMiddleware
{
/**
* Run the request filter.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($request->input('age') <= 200) {
//Do whatever you want, in this case I redirect home
return redirect('home');
}
return $next($request);
}
}

Laravel middleware 'except' rule not working

I have a controller with the following in the constructor:
$this->middleware('guest', ['except' =>
[
'logout',
'auth/facebook',
'auth/facebook/callback',
'auth/facebook/unlink'
]
]);
The 'logout' rule (which is there by default) works perfectly but the other 3 rules I have added are ignored. The routes in routes.php look like this:
Route::group(['middleware' => ['web']],function(){
Route::auth();
// Facebook auth
Route::get('/auth/facebook', 'Auth\AuthController#redirectToFacebook')->name('facebook_auth');
Route::get('/auth/facebook/callback', 'Auth\AuthController#handleFacebookCallback')->name('facebook_callback');
Route::get('/auth/facebook/unlink', 'Auth\AuthController#handleFacebookUnlink')->name('facebook_unlink');
}
If I visit auth/facebook, auth/facebook/callback or auth/facebook/unlink whilst logged in I get denied by the middleware and thrown back to the homepage.
I've tried specifying the 'except' rules with proceeding /'s so they match the routes in routes.php exactly but it makes no difference. Any ideas why these rules are being ignored, whilst the default 'logout' rule is respected?
Cheers!
You need to pass the method's name instead of the URI.
<?php
namespace App\Http\Controllers;
class MyController extends Controller {
public function __construct() {
$this->middleware('guest', ['except' => [
'redirectToFacebook', 'handleFacebookCallback', 'handleFacebookUnlink'
]]);
}
}
Since Laravel 5.3, you can use fluent interface to define middlewares on controllers, which seems cleaner than using multidimensional arrays.
<?php
$this->middleware('guest')->except('redirectToFacebook', 'handleFacebookCallback', 'handleFacebookUnlink');
I solved this issue in my Middleware by adding this inExceptArray function. It's the same way VerifyCsrfToken handles the except array.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class MyMiddleware
{
/**
* Routes that should skip handle.
*
* #var array
*/
protected $except = [
'/some/route',
];
/**
* Determine if the request has a URI that should pass through.
*
* #param Request $request
* #return bool
*/
protected function inExceptArray($request)
{
foreach ($this->except as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}
if ($request->is($except)) {
return true;
}
}
return false;
}
/**
* Handle an incoming request.
*
* #param Request $request
* #param Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// check user authed or API Key
if (!$this->inExceptArray($request)) {
// Process middleware checks and return if failed...
if (true) {
// Middleware failed, send back response
return response()->json([
'error' => true,
'Message' => 'Failed Middleware check'
]);
}
}
// Middleware passed or in Except array
return $next($request);
}
}
If you are trying to follow the Laravel Documentation, an alternative solution to this is suggested by adding routes to the $except variable in the /Http/Middleware/VerifyCsrfToken.php file. The documentation says to add them like this:
'route/*'
But I found the only way to get it to work is by putting the routes to ignore like this:
'/route'
When assigning middleware to a group of routes, you may occasionally need to prevent the middleware from being applied to an individual route within the group. You may accomplish this using the withoutMiddleware method:
use App\Http\Middleware\CheckAge;
Route::middleware([CheckAge::class])->group(function () {
Route::get('/', function () {
//
});
Route::get('admin/profile', function () {
//
})->withoutMiddleware([CheckAge::class]);
});
for more information read documentation laravel middleware
Use this function in your Controller:
public function __construct()
{
$this->middleware(['auth' => 'verified'])->except("page_name_1", "page_name_2", "page_name_3");
}
*replace page_name_1/2/3 with yours.
For me it's working fine.
I have this solved, and here's what I am doing. Aso, I just realized this is very similar to what cmac did in his answer.
api.php
Route::group(['middleware' => 'auth'], function () {
Route::get('/user', 'Auth\UserController#me')->name('me');
Route::post('logout', 'Auth\LoginController#logout')->name('logout');
});
LoginController.php
class LoginController extends Controller
{
use AuthenticatesUsers, ThrottlesLogins;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
// ...
/**
* If the user's session is expired, the auth token is already invalidated,
* so we just return success to the client.
*
* This solves the edge case where the user clicks the Logout button as their first
* interaction in a stale session, and allows a clean redirect to the login page.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
$user = $this->guard()->user();
if ($user) {
$this->guard()->logout();
JWTAuth::invalidate();
}
return response()->json(['success' => 'Logged out.'], 200);
}
}
Authenticate.php
class Authenticate extends Middleware
{
/**
* Exclude these routes from authentication check.
*
* Note: `$request->is('api/fragment*')` https://laravel.com/docs/7.x/requests
*
* #var array
*/
protected $except = [
'api/logout',
];
/**
* Ensure the user is authenticated.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try {
foreach ($this->except as $excluded_route) {
if ($request->path() === $excluded_route) {
\Log::debug("Skipping $excluded_route from auth check...");
return $next($request);
}
}
// code below here requires 'auth'
{ catch ($e) {
// ...
}
}
I over-engineered it slightly. Today I only need an exemption on /api/logout, but I set the logic up to quickly add more routes. If you research the VerifyCsrfToken middleware, you'll see it takes a form like this:
protected $except = [
'api/logout',
'api/foobars*',
'stripe/poop',
'https://www.external.com/yolo',
];
That's why I put that "note" in my doc above there. $request->path() === $excluded_route will probably not match api/foobars*, but $request->is('api/foobars*') should. Additionally, a person might be able to use something like $request->url() === $excluded_route to match http://www.external.com/yolo.
You should pass the function name to 'except'.
Here's an example from one of my projects:
$this->middleware('IsAdminOrSupport', ['except' => [
'ProductsByShopPage'
]
]);
This means the middleware 'IsAdminOrSupport' is applied to all methods of this controller except for the method 'ProductByShopPage'.

Resources