This article is not maintained any longer and likely not up to date. DuckDuckGo might help you find an alternative.

Only allow owner to update their user profile in Laravel with a policy

The problem: Only owners should be allowed to edit their profiles

The solution: Use a policy for the User model

The step by step explanation

Step 1: Write a failing test user_can_update_own_profile

// tests/Feature/Http/Controllers/UserControllerTest.php

<?php

namespace Tests\Feature\Http\Controllers;

use App\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class UserControllerTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function user_can_update_own_profile()
    {
        $user = factory(User::class)->create();
        $response = $this->actingAs($user)->post(route('user.profile.update', $user), [
            'experience_years' => 5,
        ]);

        $response->assertSuccessful();
        $user->refresh();
        $this->assertEquals(5, $user->experience_years);
    }
}

Step 2: Fix the test to assert that you can update your own profile

// Migration: database/migrations/2014_10_12_000000_create_users_table.php
// Your date in the filename should differ

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->integer('experience_years')->nullable(); // ADD: nullable integer
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('users');
    }
}

// User Model: app/User.php

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    protected $fillable = [
        'name', 'email', 'password', 'experience_years' // ADD: 'experience_years'
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

// Routes: routes/web.php

<?php

use Illuminate\Support\Facades\Route;

Route::post('/{user}/profile/update', 'UserController@update')->name('user.profile.update');

// Controller: app/Http/Controllers/UserController.php

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function update(Request $request, User $user)
    {
        $user->update($request->all());
    }
}

Step 3: Try to update someone else's profile – and fail

/** @test */
    public function user_cannot_update_foreign_profile()
    {
        $user = factory(User::class)->create();
        $foreign_user = factory(User::class)->create([
            'experience_years' => 2,
        ]);
        $response = $this->actingAs($user)->post(route('user.profile.update', $foreign_user), [
            'experience_years' => 5,
        ]);

        $response->assertForbidden();
        $foreign_user->refresh();
        $this->assertEquals(2, $foreign_user->experience_years);
    }

Step 4: Add a policy to disallow changing someone else's profile

// Policy: app/Policies/UserPolicy.php

<?php

namespace App\Policies;

use App\User;
use Illuminate\Auth\Access\Response;
use Illuminate\Auth\Access\HandlesAuthorization;

class UserPolicy
{
    use HandlesAuthorization;

    public function update(User $user, User $user_model)
    {
        return $user->id === $user_model->id ? Response::allow() : Response::deny();
    }
}

// Routes with policy: routes/web.php

<?php

use Illuminate\Support\Facades\Route;

Route::post('/{user}/profile/update', 'UserController@update')->name('user.profile.update')->middleware('can:update,user');

The ->middleware('can:update,user) means: Authorize the update() action and pass the user URL parameter to the policy (that's our $user_model in the policy).

Repo and extension

Category: Tech

Created: 2020-04-10, Last Updated: 2020-04-10

Did this help you?