MahmoudMonem's avatar

Only allow Course Owner to access course route [LARAVEL MIDDLEWARE]

I have many to many relationship tables [ courses - users & course_user ] . I would like to add a middleware that prevent non-owners from accessing course page.

I would like to write : if course id belongs to auth user return next request. else

return back()->with('error','Purchase the course first to access its content');

so I though of creating an "IsCourseOwner" Middleware, but I'm stuck with how to define the conditional inside the handle request.

Course Model:

 public function users(){return $this->belongsToMany(User::class)->withTimestamps();}

User Model:

    public function courses() {return $this->belongsToMany(Course::class)->withTimestamps();}

web.php

Route::get('courses/course/{id}/{slug?}', ['middleware' => 'isCourseOwner','uses'=>"PagesController@singleCourse", "as"=> "courseSingle"]);

My table data looks something like that

course_id : 1 | user_id : 5

My URL has the course ID like course/5- Phogotgraphy-for-beginners

0 likes
13 replies
jlrdw's avatar

Just a suggestion, many times I find it easier to protect the actual method and use the authorized users id.

This is just an example:

public function update(Request $request, Post $post) {
    if ($post->author !== auth()->user()->id || auth()->user()->cannot('edit posts'))
        abort(404);// or redirect, or whatever action 
    }
    //rest of method if all okay
}

Just it seems like you can fine-tune so much more in a controller verses a route for certain situations.

MahmoudMonem's avatar

Hey JLRDW thanks for the feedback .. I'm kinda new to Laravel and sometimes I get stuck because either I don't know or because something is very hard for me to comprehend even if i read it ..

I tried something like this in the IsCourseOwner Middleware .. but still didn't work .. what do you think ??

public function handle($request, Closure $next)
    {

        $course_id = $request->id;
        $course = Course::findOrFail($course_id);

        if ($course->user_id != auth::user()->id) {
            return back()->with('error','Purchase the course first to access its content');
        }

        return $next($request);
    }

I get returned back with error. still I'm sure something is wrong in the conditional .

jlrdw's avatar

It looks okay. But you don't need middleware for every possible thing.

In the free course: https://laracasts.com/series/laravel-6-from-scratch there are at least 5 free videos covering authorization. Jeffrey covers many techniques.

But if the above works, use it. But still consider viewing the videos.

1 like
MahmoudMonem's avatar

It didn't work unfortunately .. something is still wrong. and great .. will definitely watch the authorization part in the course... thanks :)

MahmoudMonem's avatar

Hello again .. I tried the following but still not working

1- Created a new Policy called CoursePolicy with the following function

public function show(User $user, Course $course)
    {
         return $user->id === $course->user_id;
    }

2- Binded the policy to the Course Model like so :

protected $policies = [
    Course::class => CoursePolicy::class,
];

3- In my Controller .. used authorizeForUser

public function show(Course $course)
    {

        $this->authorizeForUser(Auth::user(), 'show', [$course]);

    return $this->makeResponse('courses.show', compact('course'));
      
    }

End Result ::

me [ as an auth user ] .. blocked from all courses views .. even the one that I'm attached to ...

What am I missing here :(

extjac's avatar

what about adding something like this in your Course Model.

    public static function boot()
    {
        parent::boot();

        if ( auth()->check() ) 
        {
            static::addGlobalScope('user_id', function (Builder $builder) {
                return $builder->where( 'user_id', auth()->id() );
            });
        }
    }
MahmoudMonem's avatar

Hey extjac ..

This threw an error

Argument 1 passed to App\Course::App\{closure}() must be an instance of App\Builder, instance of Illuminate\Database\Eloquent\Builder given, called in C:\xampp\htdocs\Yadros\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Builder.php on line 954
jlrdw's avatar

In this query

    public function scopegetPets($query, $petsearch = '')
    {
        $petsearch = $petsearch . "%";
        $query->where('petname', 'like', $petsearch);
        if (ChkAuth::chkRole('admin') === false) {  // ignore, custom RBAC
            $userid = Auth::user()->id;
            $query->where('owner_id', '=', $userid);
        }
        $results = $query->orderBy('petname', 'asc')->get();
        return $results;
    }

Just "test data". But notice this part:

$query->where('owner_id', '=', $userid);

owner_id being an FK. So in the above I am only listing "pets" that belong to

the logged in user via the userid matching the FK ownerid. However an admin sees all.

So, authorization works with the query where you use FK's.

Look at https://laravel.com/docs/8.x/eloquent-relationships#eager-loading and how you can use with to load related data.

extjac's avatar

You need to add below to the model....

Course Model

use Illuminate\Database\Eloquent\Builder;
MahmoudMonem's avatar

I got SQL error

I don't have user_id in courses table .. there is a table called course_user .. where user_id is attached to course_id

Illuminate\Database\QueryException
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'user_id' in 'where clause' (SQL: select count(*) as aggregate from `courses` where `user_id` = 1)
MahmoudMonem's avatar

I kinda make sense of the example, but my brain is burning :) will try something out of it ... will also go through eager loading again .. thanks jlrdw

jlrdw's avatar

And it was just example, use your foreign keys.

MahmoudMonem's avatar

Out of trying out things ..

in my CoursePolicy.php

I changed this $course->user_id;

 public function show(User $user, Course $course)
    {
         return $user->id === $course->user_id;
    }

to something like this

  public function show(User $user, Course $course)
    {
         return $user->id === 1;
    }

Result :: course views were successfully blocked from all users except User with ID 1 .. so technically the Authorize function is working & the problem is how I'm retrieving the User ID from Pivot table. will keep trying

Please or to participate in this conversation.