Prevent user from accessing some routes in Laravel - laravel

Working on a Laravel application and I have some routes, the routes are of a multi step form. I need to prevent the user from accessing the last route (which directs to last page of the form) before accessing or filling in the previous routes.
Routes
Route::get( '/first', 'TrController#payQuote')->name('b2c.payquote');
Route::get( '/second', 'TrController#emailQuote')->name('b2c.sendquote');
Route::get( '/receipt', 'TrController#getReceipt')->name('b2c.receipt');
Route::get( '/success', 'TrController#getSuccess')->name('b2c.success');

You could create a middleware class and then use the middleware directly in your routes file.
Example:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckPermission
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// TODO: Add your logic here.
if ($request->age <= 200) {
return redirect('home');
}
return $next($request);
}
}
Then in your routes file:
Route::get('admin/profile', function () {
//
})->middleware(CheckPermission::class);

There are multiple ways to do it. One way could be that you can check http-referrer on the last page and if it is the route previous to the last one then you allow it otherwise redirect it to the previous page. This can be implemented for every page.
Other way could be database driven. For every page visit you can have an entry in the database and check on the next page if there is an entry otherwise redirect him to wherever you want.

Related

laravel9 middleware not loaded when frontend and admin are seperated

I am doing the separation of frontend and admin. After several days of studying, It seems ok now. My github: https://github.com/ronrun/laravel-multiauth
Currently there are 4 versions.
There are 3 branches, the branch 9.x_two-tables has two version.
commit: frontend and admin login can login separately
The structure:
app\Http\Controller\Admin\Auth\LoginController.php
app\Http\Controller\Admin\DashboardController.php
app\Http\Controller\Auth\LoginController.php
app\Http\Controller\HomeController.php
commit: more seperation!
app\Admin\Console
app\Admin\Exceptions
app\Admin\Http\Controllers
app\Admin\Http\Middleware
app\Admin\Providers
app\Frontend\Console
app\Frontend\Exceptions
app\Frontend\Http\Controllers
app\Frontend\Http\Middleware
app\Frontend\Providers
app\Models
In short, it's
app\Admin
app\Frontend
app\Models
During the studying process, lots of questions I want to ask. Now all seems working fine. But I still have questions about the final commit: more seperation!
Question: Many files inside app\Frontend not loaded, only app\Admin's files are used.
The two files:
app\Admin\Http\Middleware\Authenticate.php
app\Frontend\Http\Middleware\Authenticate.php
I expected that when I am not logged in, but visite a page should login, the Authenticate's redirectTo() should do the redirect work. But only Admin's Authenticate.php is loaded. I clear the content of Frontend's Authenticate.php, make it compeletly empty, NO ERRORS. But If I clear the content of Admin's Authenticate.php, it says this class cannot found.
I found the RedirectIfAuthenticated.php is the same. Then I delete frontend's middleware folder, still working fine, no errors.
Then I delete app\Frontend\Console, Exceptions, no errors. The login functions of Frontend and Admin both working. But app\Frontend\Providers cannot be deleted, or error shows.
bootstrap\app.php
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Frontend\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Frontend\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Frontend\Exceptions\Handler::class
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Admin\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Admin\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Admin\Exceptions\Handler::class
);
Why does it only loads files inside app\Admin ?
Why no errors, login logout functions still work, when many Frontend files are deleted?
Any suggestions to improve the codes, I'd appreciate it.
To keep the frontend and admin separate, basically need:
separate routes file (say web.php, api.php & admin.php)
separate redirectTo routes in RedirectIfAuthenticated
separate redirectTo routes in Authenticate
To achieve that keep all default middleware & providers under app folder -
|-app
|-Http
|-Middleware
-Authenticate.php
-EncryptCookies.php
-PreventRequestsDuringMaintenance.php
-RedirectIfAuthenticated.php
-TrimStrings.php
-TrustHosts.php
-TrustProxies.php
-VerifyCsrfToken.php
|-Providers
-AppServiceProvider.php
-AuthServiceProvider.php
-BroadcastServiceProvider.php
-EventServiceProvider.php
-RouteServiceProvider.php
|-Frontend
|-Http
|-Controllers
|-FrontendControllers
|-Middleware
|-FrontendSpecificMiddleware (if any)
|-Requests
|-FrontendFormRequests (if any)
|-Languages
|-Other
|-Admin
|-Http
|-Controllers
|-AdminControllers
|-Middleware
|-AdminSpecificMiddleware (if any)
|-Requests
|-AdminFormRequests (if any)
|-Languages
|-Other
|-routes
|-web.php
To load routes, in App\Providers\RouteServiceProvider
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
Route::middleware('web')
->prefix('admin')
->as('admin.')
->group(app_path('Admin/routes/web.php'));
});
To set the redirect route for successful authentication
<?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(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #param string|null ...$guards
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
$prefix = $request->route()->getPrefix();
return $prefix == '/admin' ? redirect(route('admin.home') : redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}
To set redirect route for unsuccessful authentication
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* #param \Illuminate\Http\Request $request
* #return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
$prefix = $request->route()->getPrefix();
return $prefix == '/admin' ? route('admin.login') : route('login);
}
}
}
You can also have App\Frontend\FrontendServiceProvider and App\Admin\AdminServiceProvider if you want and register them under providers array in config/app.php
To summarise, take cue from Domain Driven Design (DDD) approach. You can read about DDD at:
Stitcher - Laravel Beyond Crud - Domain Oriented Laravel
Conciliating Laravel and DDD

How to ensure a new route hits correct middleware in laravel

I have a laravel 5.5 app I am working on and it has an existing route which serves up html ready to be rendered to pdf:
Route::get('wkhtml/read/{documentId}/{pageId?}', $namespace . 'WkhtmlController#getRead')
->name('wkhtml.read')
->middleware('wkhtml');
This all works fine and when you navigate to the page, it shows the page ready to be rendered.
I want to make a differentiation between the pages shown here and pages which are going to be downloaded, so I added this route:
Route::get('wkhtml/download/{documentId}/{pageId?}', $namespace . 'WkhtmlController#getDownload')
->name('wkhtml.download')
->middleware('wkhtml');
If I navigate to the url eg app.localhost/wkhtml/download/123, instead of showing the pages, the user is being redirected to the login page. Nothing else has changed, so it is a bit confusing.
The WKHTMLFilter looks like this:
<?php
namespace App\Http\Middleware;
use Closure;
use App\Services\Document\Author;
use Illuminate\Http\Request;
class WKHTMLFilter
{
/**
* Handle an incoming request to one of the wkhtml routes
*
* #param Request $request
* #param Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// If it's coming from the wkhtml
if (!Author::isWkhtml($request)) {
return response()->make('Not wkhtml, not allowed here', 403);
}
return $next($request);
}
}
The app/Http/Kernel.php has this:
protected $routeMiddleware = [
...
'wkhtml' => \App\Http\Middleware\WKHTMLFilter::class,
];
The request doesn't appear to be hitting App\Services\Document\Author#isWkhtml, as I placed a die-dump there:
public static function isWkhtml(Request $request)
{
dd('here');
At the moment though, the request is not even hitting this filter.
How can I get the request to use the filter/middleware, if not like above?
Thanks in advance.
So it turns out in Laravel there are exposed routes in the AuthenticatedSession middleware, I just needed to add my new route:
protected $publicRoutes = [
'wkhtml.read', // existing route
'wkhtml.download', // new route
...
];

Laravel conditional routing based on database value

I'm developing an admin panel with laravel. I'm putting a preference for an end user to choose if the site is down (for maintenance or similar purpose).
The preference will be stored as boolean in the database. Based on this value frontend will be routed to a custom view if the site is down.
(Site will be hosted on a shared host, no SSL. Using artisan commands are not an option.)
Currently, I can get "site_is_down" value from the database at boot time with a custom method in AppServiceProvider.php's register() method.
But I'm not sure how can I route calls based on this value in the routes file.
I have two named route groups (Frontend and Backend) and standard Auth::routes() in routes/web.php. Only frontend routes should be conditionally routed. Backend and Auth should be excluded. (So the user can access Backend panel).
I'm trying to achieve something like this:
(I know this is not proper syntax, I'm trying to explain my mind)
<?php
if (config('global.site_is_down') === true) {
//Route all frontend route group to maintenance view ->except(Backend and auth)
} else {
//Route all as normal
}
Create a middleware:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckMaintainaceMode
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (config('global.site_is_down')/*or what ever logic you need*/) {
return redirect('mainainance-mode-url');
}
return $next($request);
}
}
then use this middleware in frontend routes
Route::get('/frontend', function () {
//
})->middleware('CheckMaintainaceMode');
or
Route::group(['middleware' => ['CheckMaintainaceMode']], function () {
//
});

What is use of middleware in Laravel?

I'm not clear with the concept of middleware in Laravel. What does laravel middleware do? Please provide an example if possible.
Middleware is something that is placed between two requests.
Suppose that you need to make sure that when user access to a specific group of routes he/she is authenticated.
There are two option:
Add in every controller the code to check if user is logged in ( in this example we do not consider a parent controller )
Use a middleware
In the first case you should write in each controller the same code.
With the middleware you have a piece of code that you can re-use in multiple section of your application.
Let's suppose that we want to create a Middleware that need to check if the user is logged in:
namespace App\Http\Middleware;
use Closure;
class UserIsLoggedIn
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (!auth()->user()) {
return redirect('home');
}
return $next($request);
}
}
Now with this code we can check our user where we need.
First of all since this is a custom middleware you need to register it in the app/Http/Kernel.php file in the $routeMiddleware property:
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
// ...
'isLoggedIn => \App\Http\Middleware\UserIsLoggedIn::class,
];
Let's assume that you have a group of routes that need to check the user is logged in:
Route::get('admin/profile', function () {
//
})->middleware('isLoggedIn');
Now all the routes in this group will check if the user is logged otherwise he will be redirect to home.
Now assume that you have another controller that need to make sure that the user is logged in, now you can re-use the middleware to do that:
class MyController extend Controller {
function __construct(){
$this->middleware('isLoggedIn');
}
}
So middleware help you to organize the login and re-use pieces of code for specific tasks.
Laravel has a lot of documentation about middleware that you can find here

How to prevent Laravel Routes from being accessed directly (i.e. non-ajax requests)

In my project, I am using Laravel purely as a backend api and all frontend is handled by Angular javascript. At the moment, the Laravel routes can be accessed directly and it will cough out all the data in Json that shows in the browser. I want to put a restriction on it so Laravel only responds to Ajax requests and nothing else.
I read this post here which has a solution for Laravel 4 that is by adding a restriction in filter.php. But as of Laravel 5.1, filters are no longer used and I believe Middleware can be used to do the same. However, I am not sure how to go ahead changing the Laravel 4 solution in that SO answer from filter to Middleware.
Can someone share your ideas on how to prevent Laravel 5.1 routes from being accessed directly please?
Laravel 4 solution using filter.php:
In filter.php declare this filter:
Route::filter('isAJAX', function()
{
if (!Request::AJAX()) return Redirect::to('/')->with(array('route' => Request::path()));
});
Then put all your routes that you only want accessible via AJAX into a group. In your routes.php:
Route::group(array('before' => 'isAJAX'), function()
{
Route::get('contacts/{name}', ContactController#index); // Or however you declared your route
... // More routes
});
Create the middleware file app/Http/Middleware/OnlyAjax.php with this content:
<?php
namespace App\Http\Middleware;
class OnlyAjax
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, \Closure $next)
{
if ( ! $request->ajax())
return response('Forbidden.', 403);
return $next($request);
}
}
Then register your middleware in the file app/Http/Kernel.php
<?php namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* #var array
*/
protected $middleware = [
//... your original code
];
/**
* The application's route middleware.
*
* #var array
*/
protected $routeMiddleware = [
//... your original code
'ajax' => \App\Http\Middleware\OnlyAjax::class,
];
}
And finally attach the middleware to any route or group of routes you want to make only accessible via AJAX. i.e:
/// File: routes/web.php
// Single route
Route::any('foo', 'FooController#doSomething')->middleware('ajax');
// Route group
Route::middleware(['ajax'])->group(function () {
// ...
});

Resources