Phase 3 – Authentication & Authorisation

Phase 3 – Authentication & Authorisation

  • Authentication is checking who a user is.
  • Authorisation is checking what a user can do.

Authentication

Laravel comes with authentication out of the box, I used the following two simple commands:

composer require laravel/ui

php artisan ui vue --auth

This will scaffold all of the routes and views we need for authentication, as below:

And our app’s right corner is having the following two buttons:

The Authenticated User

From now on we can get the currently authenticated user from anywhere in the app like $user = Auth::user(); or to get his ID we can say something like: $id = Auth::id();

Similarly we can check if the user is logged in by something like:

if (Auth::check()) { // Do something }

Authorisation 

Result’s Ownership

Until now, every visitor (guest) can create a result and can edit any result. And this is obviously not what we want for our app.

We should allow only the registered users to add new results or to upload files to the database. And those users will become owners for those results, so next time they will log in; they can edit their own results or delete them.

And to do so, first I added a new policy called ResultPolicy by using Artisan:

php artisan make:policy ResultPolicy --model=Result

This will generate the basic template file for adding the rules for accessing the different methods of CRUD.

Associate a user with a result

I created a new migration file and called it add_user_id_to_results.php to add another column to the current Results table so we’ll have the Authorised user’s ID who created that result record, so in the next stage, we a logged-in user can edit or delete his own records only. And by adding the ->onDelete(‘cascade’) we can handle deleting all the records that have been created by that user if we delete him from the system.

    public function up()
    {
        Schema::table('results', function (Blueprint $table) {
            $table->unsignedBigInteger('user_id')->default('1');
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        });
    }

I included the default value=’1′ so all the existing records will have at least a user to associate with. I edited this value later on in the database manually for testing, so we can have three different collections of records, each collection belongs to a different user.

I added the following condition to assure that only the owner of the record can edit that record by comparing the authorised user’s ID with the record user_id value, so if they match then proceed, if not then throw 403 not authorized access error.

$user->id === $result->user_id;

<?php

namespace AppPolicies;

use AppResult;
use AppUser;
use IlluminateAuthAccessHandlesAuthorization;

class ResultPolicy
{
    use HandlesAuthorization;

// Guests can view the All Results page 
    public function viewAny(?User $user)
    {
        return true;
    }

// Guests can view a single result individually 
    public function view(?User $user, Result $result)
    {
        return true;
    }

// Only registered and authorised users can create new Result records 
    public function create(User $user)
    {
        return $user !==null;
    }

// The registered and authorised users can edit/update their own Result records 
    public function update(User $user, Result $result)
    {
        return $user->id === $result->user_id;
    }

// The registered and authorised users can delete their own Result records 
    public function delete(User $user, Result $result)
    {
        return $user->id === $result->user_id;
    }

}

And for a second layer of protection and for a better UX design we should also hide the action buttons for not authorised user. Here we can wrap the @if(Auth::user()) directive around the Add New Country button and around the actions column in the results table.

I also added the directive @can(‘update’, $result) around the Update button within the form on actions column and the directive @can(‘delete’, $result) around the Delete button.

So, now Laravel will check the policy file to see if the current user’s id matches with the result line, and if it was, so he is the owner and he can edit or delete. And if it happened and the user knows already what URL to send manipulate with records that not belong to him, the policy also will tell Laravel not to allow him.

@if(Auth::user())
<td>
  <form method="POST" action="/results/{{ $result->id }}">
    @csrf
    @method('DELETE')

    @can('update', $result)
    <a href="/results/{{ $result->id }}/edit" type="button" class="btn btn-success btn-action"><i
        class="fa fa-edit bk-none" title="Edit"></i></a>
    @endcan
    @can('delete', $result)
    <button class="btn btn-danger" type="submit" title="Delete"
      onclick="return confirm('Are you sure you want to delete this result?')"><i
        class="fa fa-trash bk-none"></i></button>
    @endcan
  </form>
</td>
@endif

The Edit-Result problem

I faced the following problem when I was trying to edit an authorised user’s result; which is the 403 Unauthorised error. Even though the result’s user-id is equal to the current-user id, or the Auth::id() !

And after a long investigation and debugging process it turns out that I should pass on the object Result $result rather than the $id of the then search for a result with that id.

The following code didn’t work:

    public function edit($id)
    {
      	$result = Result::findOrFail($id);
        return view('results.edit', compact('result')); 
    }

But this one works fine:

    public function edit(Result $result)
    {
        return view('results.edit', compact('result')); 
    }

Everything in my CRUD works fine now.