Back to Blog
Lesson 29 of the Laravel Fundamentals: From Zero to Your First App course
LaravelJune 25, 20263 min read

Task Manager: Securing the Application with User-Scoped Data

Learn how to secure your Laravel Task Manager by associating tasks with users and filtering data so users can only view and manage their own personal tasks.

LaravelSecurityAuthenticationDatabaseBeginnerphpbackend

Previously in this course, we explored Introduction to Authentication: Securing Your Laravel Application to handle logins and session management. Now that your users can sign in, we need to ensure they aren't seeing or editing each other's work.

In this lesson, we will implement user-scoped data. This is the final step in moving from a shared, public list to a private, secure Task Manager.

Adding User Ownership to Tasks

Right now, our tasks table is agnostic—any task exists for anyone. To fix this, we need to establish a relationship between our User model and our Task model.

1. Update the Database Schema

First, we need to add a user_id column to our tasks table. If you've been following along with Task Manager: Displaying Real Database Records, you already have a migration file. Create a new migration to add the column:

Bash
php artisan make:migration add_user_id_to_tasks_table --table=tasks

In the new migration file, add the column:

PHP
public function up()
{
    Schema::table('tasks', function (Blueprint $table) {
        $table->foreignId('user_id')->constrained()->onDelete('cascade');
    });
}

Running php artisan migrate updates our schema. By using constrained(), Laravel automatically assumes a foreign key relationship to the users table. The onDelete('cascade') ensures that if a user deletes their account, their tasks are automatically removed as well.

2. Associate Tasks with the Authenticated User

When creating a new task, we must capture the current user's ID. In your TasksController, you shouldn't rely on hidden form inputs (which can be spoofed). Instead, use the auth() helper.

PHP
public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|max:255',
    ]);

    #6A9955">// Associate the task with the logged-in user
    $request->user()->tasks()->create($validated);

    return redirect('/tasks');
}

By calling $request->user()->tasks()->create(...), Laravel automatically assigns the user_id of the authenticated user to the new record.

Filtering Tasks by User ID

Now that our database records have owners, we must update our index logic to hide data that doesn't belong to the current user.

Updating the Controller

In your TasksController@index method, instead of Task::all(), we filter by the authenticated user:

PHP
public function index()
{
    #6A9955">// Retrieve only tasks belonging to the authenticated user
    $tasks = auth()->user()->tasks;

    return view('tasks.index', ['tasks' => $tasks]);
}

If a user tries to access a task ID that doesn't belong to them (e.g., via a direct URL), they shouldn't see it. We will cover how to prevent unauthorized access to specific records using policies and route model binding in the next few lessons, but filtering the collection is the essential first step.

Hands-on Exercise

  1. Run the migration to add the user_id column.
  2. Update your existing tasks: Since your current database records don't have a user_id, update them in the database or truncate the table so you can start fresh with associated tasks.
  3. Test the flow: Log in as User A, create a task, then log out and log in as User B. Ensure User B does not see the task created by User A.

Common Pitfalls

  • The "Mass Assignment" Trap: Remember to add user_id to the $fillable array in your Task model if you are using mass assignment. However, when using the $user->tasks()->create() pattern, Laravel handles the user_id assignment safely for you.
  • The "N+1" Query Problem: When you start linking users to tasks, you might accidentally trigger multiple database queries. Keep an eye on your debug bar; we will look at how to optimize this with eager loading in later lessons.
  • Missing Foreign Key Constraints: Always use constrained() in migrations. It ensures database integrity, preventing "orphaned" tasks that point to non-existent users.

Recap

We have successfully secured our application by:

  1. Adding a user_id column to our tasks table.
  2. Using the auth()->user() helper to associate new tasks with the logged-in user.
  3. Scoping our index query to auth()->user()->tasks so that data is private to the user.

Your Task Manager is now a truly personal application.

Up next: We'll dive into Route Model Binding to simplify how we retrieve and protect specific task records.

Similar Posts