I'm having a trouble with creating the "owner" middleware.
For example, I have a Articles and Usermodel associated with user_id key.
I want to add the "owner" middleware to the ArticlesController, so the only owner of that article can edit, update and delete it.
I've been searching for this issue for a while, but never found the code, which would work.
Some of them tried to make it work with Form Requests, but I'm interested in using Middleware.
Create middleware:
php artisan make:middleware OwnerMiddleware
namespace App\Http\Middleware;
use App\Article;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class OwnerMiddleware
{
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
* #return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$articleId = $request->segments()[1];
$article = Article::findOrFail($articleId);
if ($article->user_id !== $this->auth->getUser()->id) {
abort(403, 'Unauthorized action.');
}
return $next($request);
}
}
Add it to app\Http\Kernel.php:
protected $routeMiddleware = [
'owner' => 'App\Http\Middleware\OwnerMiddleware',
];
Use middleware in your routes:
Route::group(['middleware' => ['owner']], function() {
// your route
});
Alternatively you could use route and middleware parameters, it has some advantages:
Even if the request structure changes your middleware would still work
The middleware is reusable for differents resources
You can use it inside controllers
Here’s the middleware (app/Http/Middleware/AbortIfNotOwner.php):
<?php
namespace App\Http\Middleware;
use Closure;
class AbortIfNotOwner
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string $resourceName
* #return mixed
*/
public function handle($request, Closure $next, $resourceName)
{
$resourceId = $request->route()->parameter($resourceName);
$user_id = \DB::table($resourceName)->find($resourceId)->user_id;
if ($request->user()->id != $user_id) {
abort(403, 'Unauthorized action.');
}
return $next($request);
}
}
Inside app\Http\Kernel.php:
protected $routeMiddleware = [
'owner' => 'App\Http\Middleware\AbortIfNotOwner',
];
Inside your route file (app/Http/routes.php):
Route::group(['middleware' => ['owner:articles']], function() {
// your route
});
And optionally call it in the controller:
public function __construct()
{
$this->middleware('owner:articles', ['only' => ['edit', 'update']]);
}
Related
I'm using Laravel 9 with Breeze and below is all the modifications. What I did was set the session_driver to database instead of file. Right now I am using subdomains for users but later on ill be adding the ability to buy and set a domain for each tenant so that is why im using the subdomainOrDomain middleware instead of just the domain middleware by stancl. My problem is that the auth session wont be set and i dont see any entries in the tenants database within the table sessions. So no session gets set at all. The central domain also has auth and it works perfectly but just not the tenant.
App\Http\Kernel.php
protected $middlewareGroups = [
//...
'tenant' => [
\App\Http\Middleware\InitializeTenancyByDomainOrSubdomain::class,
\Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains::class,
],
'universal' => [],
];
App\Http\Middlware\InitializeTenancyByDomainOrSubdomain
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Str;
use App\Http\Middleware\InitializeTenancyByDomain;
use App\Http\Middleware\InitializeTenancyBySubdomain;
class InitializeTenancyByDomainOrSubdomain
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($this->isSubdomain($request->getHost()))
{
return app(InitializeTenancyBySubdomain::class)->handle($request, $next);
}
else
{
return app(InitializeTenancyByDomain::class)->handle($request, $next);
}
}
protected function isSubdomain(string $hostname): bool
{
return Str::endsWith($hostname, config('tenancy.central_domains'));
}
}
App\Http\Middlware\InitializeTenancyByDomain
namespace App\Http\Middleware;
use Closure;
use Stancl\Tenancy\Tenancy;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Middleware\IdentificationMiddleware;
class InitializeTenancyByDomain extends IdentificationMiddleware
{
/** #var callable|null */
public static $onFail;
/** #var Tenancy */
protected $tenancy;
/** #var DomainTenantResolver */
protected $resolver;
public function __construct(Tenancy $tenancy, DomainTenantResolver $resolver)
{
$this->tenancy = $tenancy;
$this->resolver = $resolver;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(in_array($request->getHost(), config('tenancy.central_domains'), true))
{
return $next($request);
}
return $this->initializeTenancy(
$request, $next, $request->getHost()
);
}
}
App\Http\Middlware\InitializeTenancyBySubdomain
namespace App\Http\Middleware;
use Closure;
use Exception;
use Illuminate\Support\Str;
use Illuminate\Http\Response;
use Stancl\Tenancy\Exceptions\NotASubdomainException;
class InitializeTenancyBySubdomain extends InitializeTenancyByDomain
{
/**
* The index of the subdomain fragment in the hostname
* split by `.`. 0 for first fragment, 1 if you prefix
* your subdomain fragments with `www`.
*
* #var int
*/
public static $subdomainIndex = 0;
/** #var callable|null */
public static $onFail;
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(in_array($request->getHost(), config('tenancy.central_domains'), true))
{
return $next($request);
}
$subdomain = $this->makeSubdomain($request->getHost());
if (is_object($subdomain) && $subdomain instanceof Exception) {
$onFail = static::$onFail ?? function ($e) {
throw $e;
};
return $onFail($subdomain, $request, $next);
}
// If a Response instance was returned, we return it immediately.
if (is_object($subdomain) && $subdomain instanceof Response) {
return $subdomain;
}
return $this->initializeTenancy(
$request,
$next,
$request->getHost()
);
}
/** #return string|Response|Exception|mixed */
protected function makeSubdomain(string $hostname)
{
$parts = explode('.', $hostname);
$isLocalhost = count($parts) === 1;
$isIpAddress = count(array_filter($parts, 'is_numeric')) === count($parts);
// If we're on localhost or an IP address, then we're not visiting a subdomain.
$isACentralDomain = in_array($hostname, config('tenancy.central_domains'), true);
$notADomain = $isLocalhost || $isIpAddress;
$thirdPartyDomain = ! Str::endsWith($hostname, config('tenancy.central_domains'));
if ($isACentralDomain || $notADomain || $thirdPartyDomain) {
return new NotASubdomainException($hostname);
}
return $parts[static::$subdomainIndex];
}
}
Routes\Tenant.php
Route::middleware(['tenant', 'web'])->group(function()
{
Route::get('login', [AuthenticatedSessionController::class, 'create'])
->name('login');
Route::post('login', [AuthenticatedSessionController::class, 'store']);
});
in the tenant routes i have also tried switching the web middleware to guest as is by default with laravel breeze but it trows the $errors not found error (the validation handling variable).
I'm trying to make an e-commerce admin/user authentication I use laravel 8 went register a test account and logged in and this error occured.
Error
Class 'Laravel\Fortify\Actions\Auth' not found
After i logged in a test account it was supposed to result like this
https://ibb.co/Vq5LxBk
C:\Users\ACER\laravel8ecommerce\vendor\laravel\fortify\src\Actions\AttemptToAuthenticate.php:58
This was the line 58
if(Auth::user()->utype === 'ADM')
My code on AttemptToAuthenticate.php
<?php
namespace Laravel\Fortify\Actions;
use Illuminate\Auth\Events\Failed;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\LoginRateLimiter;
class AttemptToAuthenticate
{
/**
* The guard implementation.
*
* #var \Illuminate\Contracts\Auth\StatefulGuard
*/
protected $guard;
/**
* The login rate limiter instance.
*
* #var \Laravel\Fortify\LoginRateLimiter
*/
protected $limiter;
/**
* Create a new controller instance.
*
* #param \Illuminate\Contracts\Auth\StatefulGuard $guard
* #param \Laravel\Fortify\LoginRateLimiter $limiter
* #return void
*/
public function __construct(StatefulGuard $guard, LoginRateLimiter $limiter)
{
$this->guard = $guard;
$this->limiter = $limiter;
}
/**
* Handle the incoming request.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
public function handle($request, $next)
{
if (Fortify::$authenticateUsingCallback) {
return $this->handleUsingCustomCallback($request, $next);
}
if ($this->guard->attempt(
$request->only(Fortify::username(), 'password'),
$request->filled('remember'))
) {
if(Auth::user()->utype === 'ADM')
{
session(['utype'=>'ADM']);
return redirect(RouteServiceProvider::HOME);
}
elseif(Auth::user()->utype === 'USR')
{
session(['utype'=>'USR']);
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
$this->throwFailedAuthenticationException($request);
}
/**
* Attempt to authenticate using a custom callback.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
protected function handleUsingCustomCallback($request, $next)
{
$user = call_user_func(Fortify::$authenticateUsingCallback, $request);
if (! $user) {
$this->fireFailedEvent($request);
return $this->throwFailedAuthenticationException($request);
}
$this->guard->login($user, $request->filled('remember'));
return $next($request);
}
/**
* Throw a failed authentication validation exception.
*
* #param \Illuminate\Http\Request $request
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function throwFailedAuthenticationException($request)
{
$this->limiter->increment($request);
throw ValidationException::withMessages([
Fortify::username() => [trans('auth.failed')],
]);
}
/**
* Fire the failed authentication attempt event with the given arguments.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function fireFailedEvent($request)
{
event(new Failed(config('fortify.guard'), null, [
Fortify::username() => $request->{Fortify::username()},
'password' => $request->password,
]));
}
My Routes (web php)
<?php
use App\Http\Livewire\CartComponent;
use App\Http\Livewire\CheckoutComponent;
use App\Http\Livewire\HomeComponent;
use App\Http\Livewire\ShopComponent;
use App\Http\Livewire\User\UserDashboardComponent;
use App\Http\Livewire\Admin\AdminDashboardComponent;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
// Route::get('/', function () {
// return view('welcome');
// });
Route::get('/',HomeComponent::class);
Route::get('/shop',ShopComponent::class);
Route::get('/cart',CartComponent::class);
Route::get('/checkout',CheckoutComponent::class);
// Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
// return view('dashboard');
// })->name('dashboard');
// For User or Customer
Route::middleware(['auth:sanctum', 'verified'])->group(function(){
Route::get('user/dashboard',UserDashboardComponent::class,)->name('user.dashboard');
});
// For Admin
Route::middleware(['auth:sanctum', 'verified','authadmin'])->group(function(){
Route::get('admin/dashboard',AdminDashboardComponent::class,)->name('admin.dashboard');
});
I would really appreciate a help i'm doing this for my project on school
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
from to vendor\laravel\fortify\src\Actions\AttemptToAuthenticate
but you must remove line :
use Laravel\Fortify\Actions\RouteServiceProvider;
Because this line is duplicate for RouteServiceProvider.... but maybe just for my project
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
put these two on top of
vendor\laravel\fortify\src\Actions\AttemptToAuthenticate
adding two lines on the top of AttemptToAuthenticate.php file
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
For future users;
On AttemptToAuthenticate.php, right under AttemptToAuthenticate.php,
add use Auth;
it will still throw an error class '\laravel\fortify\actions\RouteServiceProvider' not found!
To solve this,
Simply add
use App\Providers\RouteServiceProvider;, to direct it to the default
RouteServiceProvider class.
add
use Illuminate\Support\Facades\Auth;
in class vendor\laravel\fortify\src\Actions\AttemptToAuthenticate
<?php
namespace Laravel\Fortify\Actions;
use Illuminate\Auth\Events\Failed;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\LoginRateLimiter;
use Illuminate\Support\Facades\Auth;
use App\Providers\RouteServiceProvider;
class AttemptToAuthenticate
{
/**
* The guard implementation.
*
* #var \Illuminate\Contracts\Auth\StatefulGuard
*/
protected $guard;
/**
* The login rate limiter instance.
*
* #var \Laravel\Fortify\LoginRateLimiter
*/
protected $limiter;
/**
* Create a new controller instance.
*
* #param \Illuminate\Contracts\Auth\StatefulGuard $guard
* #param \Laravel\Fortify\LoginRateLimiter $limiter
* #return void
*/
public function __construct(StatefulGuard $guard, LoginRateLimiter $limiter)
{
$this->guard = $guard;
$this->limiter = $limiter;
}
/**
* Handle the incoming request.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
public function handle($request, $next)
{
if (Fortify::$authenticateUsingCallback) {
return $this->handleUsingCustomCallback($request, $next);
}
if ($this->guard->attempt(
$request->only(Fortify::username(), 'password'),
$request->filled('remember'))
) {
if(Auth::user()->utype === 'ADM')
{
session(['utype'=>'ADM']);
return redirect(RouteServiceProvider::HOME);
}
elseif(Auth::user()->utype === 'USR')
{
session(['utype'=>'USR']);
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
$this->throwFailedAuthenticationException($request);
}
/**
* Attempt to authenticate using a custom callback.
*
* #param \Illuminate\Http\Request $request
* #param callable $next
* #return mixed
*/
protected function handleUsingCustomCallback($request, $next)
{
$user = call_user_func(Fortify::$authenticateUsingCallback, $request);
if (! $user) {
$this->fireFailedEvent($request);
return $this->throwFailedAuthenticationException($request);
}
$this->guard->login($user, $request->filled('remember'));
return $next($request);
}
/**
* Throw a failed authentication validation exception.
*
* #param \Illuminate\Http\Request $request
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function throwFailedAuthenticationException($request)
{
$this->limiter->increment($request);
throw ValidationException::withMessages([
Fortify::username() => [trans('auth.failed')],
]);
}
/**
* Fire the failed authentication attempt event with the given arguments.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function fireFailedEvent($request)
{
event(new Failed(config('fortify.guard'), null, [
Fortify::username() => $request->{Fortify::username()},
'password' => $request->password,
]));
}
}
use Illuminate\Support\Facades\Auth;
This line should be added at the top of the AttemptAuthenticate.php file.
I had the same error and i solved by adding these lines. use Illuminate\Support\Facades\Auth; use App\Providers\RouteServiceProvider;
Add this to your AttemptToAuthenticate.php file.
use Illuminate\Support\Facades\Auth;
This works fine for me.
Is there a way to access a custom route parameter, same way as route "name": 'cache'=>true
Route::GET('tools/languages/{page?}', array('uses'=> 'Tools#list_languages', 'as'=>'list_languages', 'cache'=>true));
How to access cache value from Controller?
thanks,
Yes you can get your Route parameter from Middleware.
In your middleware you can get "matched route object" like this :
class MyMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$matchedRouteObject = $request->route();
$next($request);
}
}
See print_r($request->route()) there is a property that named action in this Route object. action property has all parameters of matched Route.
routes/web.php :
Route::get('tools/languages/{page?}', [
'uses' => 'Tools#list_languages',
'middleware' => 'App\Http\Middleware\MyMiddleware',
'cache' => 'value'
]);
app/Http/Middleware/MyMiddleware.php :
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Response;
class MyMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$matchedRouteObject = $this->route();
$deedVariable = $mathedRouteObject->action['cache']; // here you got your variable.
return $next($request);
}
}
Extending #Exprator answer, you could access the parameter in your controller as
public function list_languages(Request $request)
{
$request->route()->getAction()['cache']; // returns true
}
https://laravel.com/api/5.4/Illuminate/Routing/Route.html#method_getAction
I want to create two middleware to redirect the authenticated user, if it is an admin it will be redirected to the backoffice otherwise it will be redirected to a simple dashboard for simple users.
But I want to use only the users table without adding another table for the admins.
RedirectIfAuthenticated.php
<?php
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()) {
if (Auth::user()->role_id == 1)
{
return redirect('/admin/home');
}
return redirect('/dashboard');
}
return $next($request);
}
}
DashboardController.php
class DashboardController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
return view('authFront.layoutAuthenticatedUser.dashboard');
}
}
web.php
Route::get('/us-admin', function () { return redirect('/admin/home'); })->name('admin.dashboard');
// Authentication Routes...
$this->get('login', 'Auth\LoginController#showLoginForm')->name('auth.login');
$this->post('login', 'Auth\LoginController#login')->name('auth.login');
$this->post('register', 'Auth\RegisterController#register')->name('auth.register');
$this->post('logout', 'Auth\LoginController#logout')->name('auth.logout');
// Change Password Routes...
$this->get('change_password', 'Auth\ChangePasswordController#showChangePasswordForm')->name('auth.change_password');
$this->patch('change_password', 'Auth\ChangePasswordController#changePassword')->name('auth.change_password');
// Password Reset Routes...
$this->get('password/reset', 'Auth\ForgotPasswordController#showLinkRequestForm')->name('auth.password.reset');
$this->post('password/email', 'Auth\ForgotPasswordController#sendResetLinkEmail')->name('auth.password.reset');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController#showResetForm')->name('password.reset');
$this->post('password/reset', 'Auth\ResetPasswordController#reset')->name('auth.password.reset');
Route::group(['middleware' => ['auth'], 'prefix' => 'admin', 'as' => 'admin.'], function () {
Route::get('/home', 'HomeController#index');
});
Route::get('/dashboard', 'DashboardController#index');
You need to establish user roles in this case, or have an optional boolean field on the users table for is_admin. Then in middleware it would simply be something like:
class RedirectIfNotAdmin
{
/**
* 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::user()->admin()) {
return redirect()->to('user-dashboard');
}
return $next($request);
}
}
So in your case I would create a separate middleware for each role post-authorization. Then you would apply AdminMiddleware to all routes prefixed with 'admin', SimpleUserMiddleware to all other routes, for example.
You can simply check roles in RedirectIfAuthenticated middleware which do already exist by default and restrict the routes by creating an admin middleware
In app/Http/Middleware/RedirectIfAuthenticated.php
<?php
namespace App\Http\Middleware;
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 (Auth::guard($guard)->check()) {
if (Auth::user()->role_id == 1)
{
return redirect('/admin/home');
}
return redirect('/dashboard');
}
return $next($request);
}
}
In app/Http/Middleware/AuthenticateAdmin.php
<?php
namespace App\Http\Middleware;
use Closure;
use Auth;
class AuthenticateAdmin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (!Auth::check() || Auth::user()->role_id != 1)
{
if ($request->ajax())
{
return response('Unauthorized.', 401);
}
else
{
return redirect()->guest('login');
}
}
return $next($request);
}
}
In app/Http/Kernal.php add this line in $routeMiddleware array
'admin' => \App\Http\Middleware\AuthenticateAdmin::class,
In routes/web.php
Route::middleware('admin')->prefix('admin')->group(function() {
Route::get('/home', 'HomeController#index')->name('admin.home');
});
In app/Http/Controllers/Auth/LoginController.php add this function
protected function authenticated(Request $request, $user)
{
if ($user->role_id == 1) {
return redirect()->intended(route('admin.home'));
}
return redirect()->intended(route('dashboard'));
}
Hope this helps you
I'm new to laravel 5.1.
How can I use middleware parameter to protect my admin routes from users ?
something like this:
Route::group(['middleware' => 'auth:admin'], function()
/* Admin only Routes*/
{
//////
});
I have a field "role" in my "users" table that get two values:
1 for admin
2 for users
In my application, users, have their protected route.
I don't want to use packages.
You can do something like this. Inject the Guard class, then use it to check the user. You dont need to pass the parameter really. Just name your middleware 'admin' or something. The following middleware will check if the current user's role is admin, and if not, redirect to another route. You can do whatever you prefer on failure.
<?php
namespace Portal\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class Admin
{
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if($this->auth->user()->role != 'admin') {
return redirect()->route('not-an-admin');
}
return $next($request);
}
}
In case you do want to pass the parameter, you can do this:
public function handle($request, Closure $next, $role)
{
if($this->auth->user()->role != $role) {
return redirect()->route('roles-dont-match');
}
return $next($request);
}