I'm having some trouble making middleware that checks if the user owns the resource being requested.
For example, if the user goes to /playlists/1/edit, and they do not own playlist 1, it should display a 401 error.
Here's what I have so far:
class CheckOwnership {
public function handle(Request $request, Closure $next)
{
if (Playlist::find($request->route()->parameters()['playlists'])->user_id !== $request->user()->id)
{
return response('Unauthorized.', 401);
}
return $next($request);
}
}
This is terrible and only works for the Playlist resource, but I can't find any better way of doing this.
Thanks
This can easily be achieved with the newly added Form Request Validation.
You can see it in detail here (Authorizing Form Requests):
http://laravel.com/docs/5.0/validation#form-request-validation
The example given is actually about a user attempting to edit a comment they own.
Extract:
The form request class also contains an authorize method. Within this
method, you may check if the authenticated user actually has the
authority to update a given resource. For example, if a user is
attempting to update a blog post comment, do they actually own that
comment?
In your case, simply return false from the authorize method if they do no own the Playlist.
Currently, Laravel 5 does not support passing parameters to middlewares. I use sessions instead.
On your playlist controller, fetch the owner of the playlist and store it on a session. Let's say you have a playlists table with columns userID and playlistID.
public function __construct($playlistID){
$owner = Playlist::where('playlistID',$playlistID)->pluck('userID');
Session::put('OWNER',$owner);
$this->middleware('CheckOwnership',['only'=>'edit']); // apply it to edit function only, assuming you are using a route resource
}
Then, simply retrieve it on your middleware handle function.
public function handle(Request $request, Closure $next)
{
if (Session::get('OWNER') != $request->user()->id)
{
return response('Unauthorized.', 401);
}
return $next($request);
}
So far, this is a simple workaround. We have to wait till Otwell considers filter-like middlewares. Hope this helps!
For those using Laravel 8, I recommend using using Policies. This would let you organize authorization logic for specific models (e.x. the Playlist model for #ntzm).
So for example, a PlaylistPolicy class can be generated,
php artisan make:policy PlaylistPolicy --model=Playlist
and then the update function could look like this.
public function update(User $user, Playlist $playlist)
{
return $user->id === $playlist->user_id;
}
There are multiple way of enforcing this policy. If you would like to use middleware, Laravel has the can middleware that can enforce policies, so new middleware won't need to be written. In your route file this would look something like this,
Route::put('playlists/{playlist}/edit', ...)
->middleware(['can:update,playlist']);
Note: If the --model option isn't used, the policy will have to be registered manually, and example policy methods won't be automatically generated.
Related
I need to check in the rules (or authorize) if user can be authorized to delete his comment. ho can i do it? here's my rules
'user_id' => [
'required',
'exists:user,id',
],
I'm checking here if the user exists but how can i checked if user is the same as the logged one?
Right now I'm checking it in controller
public function destroy(CommentDestroyRequest $request, Comment $comment)
{
$userId = Auth::id();
if ($comment->user_id !== $userId)
return response()->json(null, Response::HTTP_FORBIDDEN);
}
but I wanted to move it
The context of the question is not correct. You are trying to use input validation to authorize users.
First; if you want to use logged in user's id to create a new record, you don't need to post it from a form, just use $request->user()->id or Auth::id() as you did. To make sure there is always an authenticated user; add auth middleware for that route (or controller method).
And on the other hand if you want to check if a user authorized to do something you should use authorization services which comes built-in with Laravel.
To accomplish that you can use Gate or Policy
Here is the documentation: https://laravel.com/docs/8.x/authorization
Let's say you want to determine if a user authorized to delete a Comment , you can do this by Writing Gates
You can define gates in your app/Providers/AuthServiceProvider.php file's boot method;
// app/Providers/AuthServiceProvider.php
use App\Models\Comment;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('delete-comment', function (User $user, Comment $comment) {
return $user->id === $comment->user_id;
});
}
Now you can use that gate with it's name -which is delete-comment here- to check authorization.
public function destroy(CommentDestroyRequest $request, Comment $comment)
{
if (Gate::denies('delete-comment', $comment)) abort(403);
// Authorization checked, you can do whatever you want
$comment->delete();
return redirect('/comments');
}
Or you can use authorize in a controller;
public function destroy(CommentDestroyRequest $request, Comment $comment)
{
$this->authorize('delete-comment', $comment);
// Authorization checked, you can do whatever you want
$comment->delete();
return redirect('/comments');
}
This will do the trick for you.
But a more convenient way to authorization in Laravel is Policies. You should definitely check and consider to use them.
Policies are classes that organize authorization logic around a
particular model or resource. For example, if your application is a
blog, you may have a App\Models\Post model and a corresponding
App\Policies\PostPolicy to authorize user actions such as creating
or updating posts.
You should save your user_id in comment section too so you can easily detect wether the user is authenticated or not
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 wonder if I can do this in Laravel Route. Let's say I have Admin, Premium and User (which can be login too by using Auth) Middleware. Also, I have controller with methods like this: index, create, edit, delete and I want Admin to be able do all those things, but Premium can only be able to access index method, and User can't access anything in this controller (he can access another controller). I know I can use except or only middleware method like this:
public function __construct()
{
$this->middleware('premium')->only('index');
$this->middleware('admin');
// or maybe $this->middleware('admin')->except('index');
}
but when I try to put these two middlewares in __construct method they will start to conflict each other, it makes sense because index method can be access by Premium but then can't be access by the Admin itself. By the way, my middleware is simply checking:
if (Auth::check()) {
if (Auth::user()->role == 'Admin') {
return $next($request);
}
}
return redirect('/home');
So, back to my question, can I have OR Middleware so I can avoid conflict from multiple middleware (which is must be AND condition when they written at the same controller constructor)?
If you change up the way your logic is thinking a little bit, the answer becomes pretty easy. You can create new middleware that checks if it can access the specific method.
So create the following middleware 'CanAccessIndex':
if (Auth::check()) {
if (Auth::user()->role == 'Admin' || Auth::user()->role == 'Premium') {
return $next($request);
}
}
return redirect('/home');
Then, you can put that middleware on the index function (instead of the premium middleware) and put your admin middleware on everything EXCEPT index. Like so:
public function __construct()
{
$this->middleware('canAccessIndex')->only('index');
$this->middleware('admin')->except('index');
}
That's one way to do it.
You need middleware group for this, and to manage these hierarchy of access layer, you can simply use Route grouping.
I will demo an example of what I mean:
Say you have auth middleware general for authenticated users (i.e everybody), then another called premium for premium member, and admin for the Admin.
Then you'll group based on the access level:
Route::middleware('auth')->group(function(){
Route::middleware('premium')->group(function(){
Route::post('/create', 'HomeController#create')->middleware('admin');
Route::put('/update/{id}', 'HomeController#update')->middleware('admin');
Route::get('/index', 'HomeController#index');
Route::put('/delete/{id}', 'HomeController#delete')->middleware('admin');
});
});
So you can have general check in your middleware with a check. It would have been much easier if you have role level say 3 for admin and 2 for premium member. So we can have for the premium middleware:
public function handle($request, Closure $next)
{
return auth()->user->role >= 2
? $next($request)
: redirect('/home');
}
This is just an example. You can do further check based on your need but more importantly, ensure your admin middleware to checks the exact role level that is allowed.
I am using a session separately other than the default authentication sessions. If an user try to access my secured page, he should have the session set. If anyone without that session try to access means, they will be redirected to error page. I am using Laravel 5.3
The user can view the below two pages only if the session variable named 'secured_user' is set. Otherwise they will be redirect to the error page
Route::get('/secured-page1', 'ValidationController#CheckSecuredLogin_1');
Route::get('/secured-page2', 'ValidationController#CheckSecuredLogin_2');
The best option would be a policy.
You can create certain constrains and couple it with your models. Policies are especially suitable for changing your logic later on.
See here: Create Policy
Within you PagesPolicy, you can add this function:
public function before(User $user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
public function seeSecurePage(User $user)
{
// Your custom Code and session handling
if(session("secured_user")) return true;
return false;
}
and in your controller.
$user->can("seeSecurePage","Pages");
If "can" fails, it will automatically redirect to error 403.
P.S.: Another possibility are Gates
You should use Laravel Middlewares to achieve this, I think middlewares are made for the work you need:
First create a new middleware by running the artisan command:
php artisan make:middleware CheckSesison
Then the CheckSession would look like this:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckSession
{
public function handle($request, Closure $next)
{
if ($session_value != 'YOUR_DESIRED_VALUE') {
return redirect('home');
}
return $next($request);
}
}
Now in your routes file you can use laravel's route middleware() method to implement it like this:
Route::get('/secured-page1', 'ValidationController#CheckSecuredLogin_1')
->middleware(CheckSession::class);
Hope this helps!
In addition to the awnser above, you could also use middleware that's used on the routes and even group them if required. It is a simple, quick and clean solution. Inside the middelware you simple check if the session you require is there and depending on the result you take any action necessary.
Laravel middleware docs
I am today in bit confusion about my website security and some extra code that is written to make the website secure. Below are 2 locations where security is applied.
Inside Route Config, To secure the route, I have used Middleware to check the user role.
Route::group(['middleware' => ['web', 'SuperAdmin', 'auth']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
I mentioned 2 Middlewares.
Auth Middleware : This is for authentication.
SuperAdmin Middleware: This is for Authorization.
Second location is Request class. Here is the code. In authorize method, again same thing is being checked as already done in route
class DepartmentRequest extends Request
{
public function authorize()
{
if(\Auth::user() == null) {
return false;
}
if(\Auth::user()->isSuperAdmin()) {
return true;
}
return false;
}
public function rules()
{
return [
'Department' => 'required',
];
}
}
Question: Should I remove check in Request class? Is that an unwanted validation to secure the request ? As route.config is already doing the job.
What's the use of authorize method? I meant, I am using Request class to validate form inputs. Should I always return true from authorize method?
yes, you should remove that checks in the Request class: if you're already doing that checks in your middleware you should not repeat them
When you specify this:
Route::group(['middleware' => ['web', 'SuperAdmin']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
You're telling laravel that, when it finds a /Create-Department route, it should trigger the handle method of these middleware: ['web', 'SuperAdmin'], before the request is sent to the DepartmentController
So, if you check for authentication and authorization in the middlewares, when the request will get to your controller you're sure that it has satisfied all the middleware it went through
Regarding the purpose of the authorize method: the authorize method is usually used to authorize the actual request basing on some policy you'd like to respect. For example, if you have a request to edit a Post model, in the authorize method you'd check that the specific user trying to edit the post has the permissions to do it (for example being the author of the post )
EDIT
Even if you want to use a middleware for your authorization, it's fine. Anyhow, usually the authorize method within form requests is used to do authorization checks on the specific request.
For instance check this example from the docs :
public function authorize()
{
$postId = $this->route('post');
//here the authorization to edit the post is checked through the Gate facade
return Gate::allows('update', Post::findOrFail($postId));
}
In conclusion: if you're doing your authentication and authorization tasks in middlewares, you don't need to repeat them in the authorize method, but keep in mind that the native purpose of the method is to authorize the specific request