I have an application which uses Laravel 5's out of the box authentication. I need to require authentication on the show method of a controller ONLY when the field called "approved" is equal to 1.
How can I require authentication using middlewares on a conditional basis such that unauthenticated users can access entries whose approved column is equal to 1, but only authenticated users can see entries where approved is equal to 0.
My constructor currently looks like this:
public function __construct(){
$this->middleware('auth', ['only' => ['edit', 'destroy', 'approve', 'markRecovered']]);
}
You may create your own middleware instead of using Laravel's default auth middleware and in that case, you may create a middleware such as 'checkApproval' using something like this:
php artisan make:middleware checkApproval
Then in your app/Http/Middleware directory you'll find the new middleware created and it'll contain the basic structure including handle method, so now you may erite code inside this middleware's handle method to check the user's state and the approved field and then either redirect to login page if condition doesn't match or allow access. here is a basic idea:
use Illuminate\Contracts\Auth\Guard;
class CheckPermission implements Middleware {
protected $auth;
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
public function handle($request, Closure $next)
{
if($this->auth->guest() && !$this->checkApproval($request))
{
return redirect('login');
}
return $next($request);
}
protected function checkApproval($request)
{
// Get the auth/logged in user
// $user = $request->user();
// Get a parameter from route
// $id = $request->route()->parameter('id')
// Check the approved field here and return true or false
}
}
Now assign the middleware a short-hand key in your app/Http/Kernel.php file. By default, the $routeMiddleware property of this class contains entries for the middleware included with Laravel. To add your own, simply append it to this list and assign it a key to use in your route/controller, for example, checkApproval so in the place of auth you may use checkApproval for the the method view in your controller.
This is an abstract idea, but you can implement one of your own now so check the documentation for more information.
Related
I'm using Laravel 5.5 and I'm trying to use Gate facade to allow admins to access resources like users. First, I define a gate in AuthServiceProvider.php like following:
Gate::define('view-users', 'App\Policies\UserPolicy#view');
Then, I write view method in Policy class like this:
public function view(Admin $admin, User $user)
{
return true;
}
And, I apply the authorization like following:
//UsersController.php
$user = User::first();
if (Gate::allows('view-users', $user)) {
$users = User::all();
return view('admin.users.list', compact('users'));
}
return abort(403);
I note that, the $user argument is useless variable and I don't need it to perform authorization.
By the way, when I use allows() method of Gate facade, it always returns false. While, when I use denies() instead, these steps work fine.
what's wrong with allows() method?!
However, corresponding to the Laravel Docs, I tested other ways to apply authorization via middleware(), Model or authorize(). But, I got the same result.
Edit:
I should note that I'm using custom guard named web_admin
Thanks for any help.
Change your policy method to this:
public function view(User $user)
{
return $user->isAdmin;
}
The first argument of the policy method is always the current authenticated user. Note that you are not required to pass the currently authenticated user to these methods. Laravel will automatically take care of passing the user into the gate Closure:
if (Gate::allows('view-users')) {
// The current user can view all users...
}
If you want to check if the current user can update a specific user your policy method would be:
public function update(User $authenticatedUser, User $beeingEditedUser)
{
return $authenticatedUser->isAdmin;
}
Then authorize like this:
if (Gate::allows('update-user', $beeingEditedUser)) {
// The current user can update the user...
}
If you're using custom guard (according to your comment), you may have 2 options:
Use forUser method:
use Illuminate\Support\Facades\Auth;
if (Gate::forUser(Auth::guard('web_admin')->user())->allows('view-users')) {
// The current user can view all users...
}
Protecting the routes, specifying the guard:
Route::middleware('auth:web_admin')->group(function () {
Route::get('/users', 'UserController#index');
});
This causes Larvel to set your default auth driver and resolve the auth user based on your custom guard.
I have a Laravel controller with destroying method and middleware with name CheckRole.
public function destroy(Request $request, $id)
{
// code to delete item
}
I am unable to use the $id in middleware CheckRole.
class CheckRole
{
public function handle($request, Closure $next)
{
}
}
And
$request->id and
$request->route('id')
are also not working with the middleware.
How to fix this?
Getting URL Parameters in Middleware
class CheckRole
{
public function handle($request, Closure $next)
{
$id = $request->route()->parameter('id');
}
}
The $id in the destroy function is the id of the resource you want to delete, so that will never be the authenticated user id a better option for checking the role might be an authorization policy instead of a middleware.
Or get the authenticated user id by using: auth()->id() or $request->user()->id
You can also add a method in your User model, and check like that for example:
class User ...
public function isAdmin()
{
return $this->role === 'admin';
}
In the middleware
$request->user()->isAdmin();
--- EDIT
So if you use a resource when you defined your route for example:
Route::resource('user', 'UsersController');
Then in your middleware you can access the id of the user like this:
$request->user
If your route is like this:
Route::delete('/users/{user}', 'UsersController#destroy');
Then again you can get it using the named parameter $request->user. Make sure that whatever you use on your route in the {} that's the name of the parameter you use to get the id from.
There's no real safe way to do this as the user can just change the ID within the form submitted, unless you just want users who can sign in to be an admin. If this is true I would use #nakov's suggestion.
If you want to have a full permission system with roles, etc I would suggestion: https://github.com/spatie/laravel-permission
A good tutorial can be found here but please do not change anything it states with the password: https://scotch.io/tutorials/user-authorization-in-laravel-54-with-spatie-laravel-permission
it should forbid that the user grabs this url:
?main_title=banner
?main_title=law
?main_title=faq
with this
if(\Auth::user()->hasRole(['super_admin']))
I am going to assume that you are using spatie/laravel-permission based on your example code.
Laravel Permission comes with built-in role middlewares
One of the ways you could use them is by grouping the routes you want to be accessible only by super admins
Route::group(['middleware' => ['role:super_admin']], function () {
// YOUR ROUTES HERE
});
It's always good to using the middlewares ,
So in your case first create a Trait for roles
public function isSuperadmin(){
return Auth::user()->role->role=='superadmin';
}
After that create a middlewar like superadmin for the superuser and in that first include your trait
use App\Traits\Roles;
after that
use Roles;
public function handle($request, Closure $next)
{
if(!$this->isSuperadmin())
{
return back();
}
return $next($request);
}
and just register the middleware in the app/http/kernal.php in protected $routeMiddleware function
'superadmin' => \App\Http\Middleware\superadmin::class,
so it's make your life very easy now you don't need to check the url or role every time , for any url you want to block for other users just use
Route::get('/?main_title=law', 'HomeController#function')->middleware('superadmin')->name('admin-dashboard-home');
so if the user role is superadmin then he is allow to assess the url you can redirect the other users or show the error message :)
I have a multiauth laravel 5.2 app, with the fallowing guards defined on config/auth.php:
...
'admin' => [
'driver' => 'session',
'provider' => 'admin',
],
'user' => [
'driver' => 'session',
'provider' => 'user',
],
...
So, admin and user.
The problem resides in the view layer, since this two loggedin guards share some views, ex:
Hello {{Auth::guard('admin')->user()->name}}
In this case the guard is hardcoded into the view to be always admin (it gives error when loggedin guard is user), but, to avoid have to do another equal view just for this little change, I would like have it dinamic, something like:
Hello {{Auth::guard(<LOGGEDIN GUARD>)->user()->name}}
PS: I know that this could be achieved getting the corresponding url segment, ex: www.site.com/pt/user/dasboard which in the case it would be segment 2, but this way the app would lose scalability, since in the future the corresponding segment may not be the same (2 in the example above)
One way to do this is to extend the Laravel authentication class in the IoC container to include, for instance, a name() method that check which guard is used for the current session, and calls user() on that Guard instance.
Another way is to simply use an if-statement in your Blade template:
#if(Auth::guard('admin')->check())
Hello {{Auth::guard('admin')->user()->name}}
#elseif(Auth::guard('user')->check())
Hello {{Auth::guard('user')->user()->name}}
#endif
However, this is a little dirty. You can clean this up a bit by using a partial, or by passing the view a variable containing the guard name, either directly from your Controller, or via a ViewComposer, and then doing:
Hello {{Auth::guard($guardName)->user()->name}}
in your View.
Extending Laravel's authentication is your best option, imo.
This will get the guard name that is used for current logged in user
Auth::getDefaultDriver()
When you log in, by default it will get you the:
'web'
Dependable through which guard you've been logged in it will get you that guard name.
This is not applicable for APIs!!! Because APIs in laravel by default don't use session.
Since Laravel 5.5, this is easy to do with the #auth template directive.
#auth("user")
You're a user!
#endauth
#auth("admin")
You're an administrator!
#endauth
#guest
You're not logged in!
#endguest
Reference: https://laravel.com/docs/5.6/blade#if-statements
In new versions of laravel use:
Auth::getDefaultDriver()
I recommend to use global helper function like
function activeGuard(){
foreach(array_keys(config('auth.guards')) as $guard){
if(auth()->guard($guard)->check()) return $guard;
}
return null;
}
Depends of Harat answer, I built a Class names CustomAuth, and it give me easy access to Auth facade methods: user() and id().
<?php
namespace App\Utils;
use Illuminate\Support\Facades\Auth;
class CustomAuth{
static public function user(){
return Auth::guard(static::activeGuard())->user() ?: null;
}
static public function id(){
return static::user()->MID ?: null;
}
static private function activeGuard(){
foreach(array_keys(config('auth.guards')) as $guard){
if(auth()->guard($guard)->check()) return $guard;
}
return null;
}
}
using auth()->guard($guard)->getName() will return two different type of values
login_admin_59ba36addc2b2f9401580f014c7f58ea4e30989d
if is an admin guard
login_web_59ba36addc2b2f9401580f014c7f58ea4e30989d
if is a web guard or depending on your use case a user guard. So you can test against that.
so a simple use case can be as stated below
if(str_contains(auth()->guard($guard)->getName(), 'admin')){
dd('is admin');
}
here if is an admin, it will show 'is admin' otherwise you get the default
Auth::getDefaultDriver()
Above will return the current guard, in your case (user or admin)
In my case I had up and running project with too often usage of auth()->user() thus I was obligated to find a way to keep using multiple guards across my app.
Use a middleware to handle overwriting the value the default guard.
Add a custom web middleware to your web group within kernel.php
protected $middlewareGroups = [
'web' => [
.....
//This cool guy here
\App\Http\Middleware\CustomWebMiddleware::class,
],
Use CustomWebMiddleware.php content:
<?php
namespace App\Http\Middleware;
use Closure;
class CustomWebMiddleware
{
public function handle($request, Closure $next)
{
$customGuard = 'custom-guard-name-goes-here';
if (auth($customGuard)->check()) {
config()->set('auth.defaults.guard', $customGuard);
}
// if you have multiple guards you may use this foreach to ease your work.
/*$guards = config('auth.guards');
foreach ($guards as $guardName => $guard) {
if ($guard['driver'] == 'session' && auth($guardName)->check()) {
config()->set('auth.defaults.guard', $guardName);
}
}*/
return $next($request);
}
}
I have multi middleware (studen, parent, admin) and create some route group with that middleware. But some route can access if user is in any of that groups and belong in any of that middleware but not if is in other middleware fot example teacher. I use something like that in docs: http://laravel.com/docs/5.1/routing#route-groups But it's work when I put one route, when add another route group with another middleware it doesn't work. Is that possible and how to make it?
When I execute php artisan route it gives me an error
[Symfony\Component\Debug\Exception\FatalErrorException]
Call to a member function inRole() on null
Laravel's route middleware is executed one by one as declared in routes.php file. Therefore, if one of them denies access by throwing an exception or returning some respoise, the next middlewares won't be executed.
In order to make that work, you'll need a single middleware that would check if current user has any of required roles. Luckily, as of Laravel 5.1 you are able to pass parameters to middleware from your routes.php file (see http://laravel.com/docs/5.1/middleware#middleware-parameters), so you'll only need one middleware class to handle all cases.
Example middleware class could look like that:
class HasAnyRole
{
public function handle($request, Closure $next, $roles)
{
// Return Not Authorized error, if user has not logged in
if (!$request->user) {
App::abort(401);
}
$roles = explode(',', $roles);
foreach ($roles as $role) {
// if user has given role, continue processing the request
if ($request->user->hasRole($role)) {
return $next($request);
}
}
// user didn't have any of required roles, return Forbidden error
App::abort(403);
}
}
Register the middleware in your Kernel.php:
protected $routeMiddleware = [
'has_any_role' => 'App\Http\Middleware\HasAnyRole',
];
Now, in your routes.php you can apply the middleware to a group like that:
//this route is available only to users with role admin or author
Route::put('post/{id}', ['middleware' => 'has_any_role:admin,author', function ($id) {
//
}]);
This should do the trick, just make sure that your User class has a hasRole method.