Jesse Luke Orange
Jesse Luke Orange

Reputation: 1999

Laravel Middleware based on a relation

In my Laravel application, administrators can be assigned to jobs, I also have super administrators that can do everything.

Administrators that are not super administrators should only be able to access jobs they are assigned to.

Let's use a rough route for illustration:

http://localhost:3000/jobs/{job}

http://localhost:3000/jobs/{job}/notes{note}

In this scenario {job} is an id acquired via route model binding and a note is attached to a job.

Administrators assigned to jobs are done so via the following relationship method:

/**
 * Get the jobs that this admin has access to via the pivot table
 *
 * @return void
 */
public function jobs()
{
    return $this->belongsToMany(JobPost::class, 'job_post_admin', 'admin_id', 'job_post_id');
}

So I can use $user->jobs

I want to be able to say the following - if you're a super admin you can go anywhere, if you're not, you should be restricted to what you've been assigned to.

So if a user only has access to http://localhost:3000/jobs/{1} and they go to http://localhost:3000/jobs/{2} they should be redirected.

I created a Middleware called Access

/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 */
public function handle($request, Closure $next)
{
    if (Auth::guard('admin')->user()) {

        $user = Auth::guard('admin')->user();

        if ($user->is_admin) {
            return $next($request);
        } else {
            if ($user->jobs->contains($job)) {
                return $next($request);
            } else {
                return response('You do not have sufficient priveledges to perform this action.', 403);
            }
        }
    } else {
        return redirect()->back();
    }
}

However, I'm confused as to how I would get the job ID from the URL.

I have a working MiddleWare that looks like this:

<?php

namespace App\Http\Middleware;

use Closure;
use Auth;

class Access
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (Auth::guard('admin')->user()) {

            $user = Auth::guard('admin')->user();

            $job = $request->vacancy;

            if ($user->is_admin) {
                return $next($request);
            } else {
                if ($user->jobs->contains($job)) {
                    return $next($request);
                } else {
                    return response('You do not have sufficient priveledges to perform this action.', 403);
                }
            }
        } else {
            return redirect()->back();
        }
    }
}

I am now interested though, as without sounding dense I haven't really acknowledged authorization in Laravel, does it have benefits over Middleware?

Upvotes: 0

Views: 497

Answers (1)

apokryfos
apokryfos

Reputation: 40690

Authentication is different to authorization. Laravel supports both.

You can do what you need using authorisation policies, there is no need for extra middleware.

First create a policy:

php artisan make:policy JobsPolicy --model=Job

This will make your boilerplate. Then you can add the actions:

class JobsPolicy {
    use HandlesAuthorization;

    //If you return true here the policy always succeeds
    public function before(User $user, $ability) { 
        if ($user->is_admin) {
            return true;
        }
    }


    public function view(User $user, Job $job) {
        return $user->jobs->contains($job);
    }

    public function create(User $user) {
        return true; //Return true if the user can create jobs
    }

    public function update(User $user, Job $job) {
          return $user->jobs->contains($job);
    }   
}

You need to also register your policy in the AuthServiceProvider in the $policies array:

 protected $policies = [
    Job::class => JobPolicy::class        
];

Then you can add the already existing middleware e.g.:

Routes::get('jobs/{job}/notes/{note}', ...)->middleware("can:view,job"); 

This will ensure that the currently authenticated user can view the job specified by the route job parameter.

There's more information in the documentation

Upvotes: 2

Related Questions