Implementing User Suspension in Your Laravel Application

Raziul Islam - Jun 30 - - Dev Community

This guide will walk through implementing user suspension in a Laravel application. This functionality allows you to temporarily or permanently suspend users and notify them accordingly.

Step 1: Add Suspension Columns to the Users Table

First, we need to update our users table to include columns for tracking suspension status and reason.

  1. Create a new migration:
php artisan make:migration add_suspension_columns_to_users_table --table=users
Enter fullscreen mode Exit fullscreen mode
  1. In the migration file, add the following code:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->timestamp('suspended_at')->nullable();
            $table->timestamp('suspension_ends_at')->nullable();
            $table->string('suspension_reason')->nullable();
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn([
                'suspended_at',
                'suspension_ends_at',
                'suspension_reason'
            ]);
        });
    }
};

Enter fullscreen mode Exit fullscreen mode
  1. Run the migration
php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Step 2: Update the User Model

  1. Open app/Models/User.php file.
  2. Use the Suspendable trait and add the necessary attribute casts:
use App\Traits\Suspendable;
...

class User extends Authenticatable
{
    ...
    use Suspendable;

    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
        'suspended_at' => 'datetime',
        'suspension_ends_at' => 'datetime',
    ];

    ...
}

Enter fullscreen mode Exit fullscreen mode

Laravel 11 and newer versions utilize casts methods for property casting:

protected function casts(): array
{
    return [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
        'suspended_at' => 'datetime',
        'suspension_ends_at' => 'datetime',
    ];
}

Enter fullscreen mode Exit fullscreen mode

Step 3: Create the Suspendable Trait

  1. Create a new PHP file at app/Traits/Suspendable.php
  2. Add the following code to that file:
<?php

namespace App\Traits;

use App\Notifications\UserSuspendedNotification;
use App\Notifications\UserUnsuspendedNotification;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Casts\Attribute;

trait Suspendable
{
    /**
     * Account is banned for lifetime.
     */
    protected function isBanned(): Attribute
    {
        return Attribute::get(
            fn () => $this->suspended_at && is_null($this->suspension_ends_at)
        );
    }

    /**
     * Account is suspended for some time.
     */
    protected function isSuspended(): Attribute
    {
        return Attribute::get(
            fn () => $this->suspended_at && $this->suspension_ends_at?->isFuture()
        );
    }

    /**
     * Suspend account and notify them.
     */
    public function suspend(string $reason, CarbonInterface $ends_at = null): void
    {
        $this->update([
            'suspended_at' => now(),
            'suspension_reason' => $reason,
            'suspension_ends_at' => $ends_at,
        ]);

        $this->notify(new UserSuspendedNotification($this));
    }

    /**
     * Un-suspend account and notify them.
     */
    public function unsuspend(): void
    {
        if (! $this->suspended_at) {
            return;
        }

        $this->update([
            'suspended_at' => null,
            'suspension_reason' => null,
            'suspension_ends_at' => null,
        ]);

        $this->notify(new UserUnsuspendedNotification($this));
    }
}

Enter fullscreen mode Exit fullscreen mode

This trait adds the suspend and unsuspend methods to the User model for suspending and unsuspending accounts easily. This also provides the is_banned and is_suspended attributes for checking suspension status.

Step 4: Create Notifications

  1. Create notification classes:
php artisan make:notification UserSuspendedNotification
php artisan make:notification UserUnsuspendedNotification
Enter fullscreen mode Exit fullscreen mode
  1. Edit app/Notifications/UserSuspendedNotification.php
namespace App\Notifications;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class UserSuspendedNotification extends Notification
{
    use Queueable;

    public function __construct(
        public readonly User $user
    ) {
    }

    /**
     * Get the notification's delivery channels.
     */
    public function via(object $notifiable): array
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     */
    public function toMail(object $notifiable): MailMessage
    {
        if ($this->user->is_banned) {
            $subject = __('Your account has been banned');
            $message = null;
        } else {
            $subject = __('Your account has been suspended');
            $message = __('Suspension will end on: :date', ['date' => $this->user->suspention_ends_at])
        }

        return (new MailMessage)
            ->subject($subject)
            ->line($subject)
            ->line(__('Reason: **:reason**', ['reason' => $this->user->suspension_reason]))
            ->line($message)
            ->line(__('If you believe this is a mistake, please contact us.'))
            ->line(__('Thank you for your understanding.'));
    }
}

Enter fullscreen mode Exit fullscreen mode
  1. Edit app/Notifications/UserUnsuspendedNotification.php
namespace App\Notifications;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class UserUnsuspendedNotification extends Notification
{
    use Queueable;

    public function __construct(
        public readonly User $user
    ) {
    }

    /**
     * Get the notification's delivery channels.
     */
    public function via(object $notifiable): array
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     */
    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject(__('Your Suspension Removed'))
            ->greeting(__('Hello :name,', ['name' => $this->user->name]))
            ->line(__('Suspension has been removed. Your account is now active.'))
            ->line(__('You can now log into your account.'))
            ->action(__('Log in'), route('login'))
            ->line(__('Thank you for staying with us.'));
    }
}

Enter fullscreen mode Exit fullscreen mode

We are almost done 😀 Let's take a look at the usage example:

$user = \App\Models\User::find(1);

// temporary suspension (for 7 days)
$user->suspend('suspension reason', now()->addDays(7));

// permanent suspension
$user->suspend('suspension reason');

// unsuspension
$user->unsuspend();

Enter fullscreen mode Exit fullscreen mode

Now, The only thing that remains is to check whether the authenticated user is suspended and restrict their access to the application. Let's do this in the next step.

Step 5: Restrict Application Access for Suspended Users

  1. Create Middleware
php artisan make:middleware CheckUserSuspension
Enter fullscreen mode Exit fullscreen mode
  1. In the middleware file app/Http/Middleware/CheckUserSuspension.php, add the following logic to handle restricted access for suspended users:
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class CheckUserSuspension
{
    public function handle(Request $request, Closure $next): Response
    {
        $user = $request->user();

        abort_if(
            $user && ($user->is_suspended || $user->is_banned),
            Response::HTTP_FORBIDDEN,
            __('Your account has been suspended or banned. Check your email for details.')
        );

        return $next($request);
    }
}

Enter fullscreen mode Exit fullscreen mode
  1. Apply Middleware to Routes:

In routes/web.php or routes/api.php apply the middleware to the routes you want to protect:

use App\Http\Middleware\CheckUserSuspension;

// Protected routes
Route::group(['middleware' => ['auth', CheckUserSuspension::class]], function () {
    Route::get('/dashboard', DashboardController::class]);
});

// Other routes

Enter fullscreen mode Exit fullscreen mode

Otherwise, you can add this middleware to the web or api middleware group to apply it to a set of routes.

Step 6: Applying to Middleware Groups (optional)

  1. Laravel 11 or newer
// file: bootstrap/app.php

use App\Http\Middleware\CheckUserSuspension;

return Application::configure(basePath: dirname( __DIR__ ))
    // other code
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            CheckUserSuspension::class,
        ]);
    })
    // other code

Enter fullscreen mode Exit fullscreen mode
  1. For Laravel 10 or older
// file: app/Http/Kernel.php

    protected $middlewareGroups = [
        'web' => [
            // other middlewares
            CheckUserSuspension::class,
        ],

        'api' => [
            // other middlewares
            CheckUserSuspension::class,
        ],
    ];

Enter fullscreen mode Exit fullscreen mode

Conclusion

By following this guide, you have successfully implemented user suspension functionality in your Laravel application. This approach keeps your User model clean and encapsulates the suspension logic within a reusable Suspendable trait.

This feature allows you to manage user access effectively by suspending and unsuspending users as needed. This not only enhances the security and control over user activities but also ensures a better user management system.

Happy coding! ❤️

. . . . . . . . . .