Skip to content

Suspending accounts

A suspended account cannot sign in, and the session it already had ends on its next request. Both halves matter: barring somebody does not reach into a session they are already holding, and without the second half a fraud case keeps working until they happen to sign out.

The contract, and why laraspring/auth knows nothing about the panel

Suspensions are written by laraspring/admin and enforced by laraspring/auth, and the two packages never name each other. Between them sits one contract in laraspring/core:

php
namespace Laraspring\Core\Contracts;

interface AccountStatus
{
    public function restriction(object $user): ?AccountRestriction;
}

laraspring/core binds an implementation that answers null for everybody, so an application with no administration package resolves this happily and pays a method call. laraspring/admin replaces the binding with one that reads its own account_bans table.

Why it returns a reason rather than a boolean. isBanned(): bool is enough to close the door and not enough to say anything at the door, and "your account has been suspended" with no cause is the support ticket the feature exists to avoid. It is the same trade MembershipQuota makes: one call serves enforcement and display, because two calls are two chances to disagree. The expiry rides along for the same reason, since a suspension until Friday and a permanent one read differently to the person serving them.

The four doors

DoorWhat happens
The password formCorrect credentials, then a refusal on the email field
A magic linkThe token is spent, then a refusal on the request screen
An OAuth callbackThe provider vouches, the application still says no
A session already openSigned out and sent to the sign-in screen, on the next request

All four go through one place, Laraspring\Auth\Support\AccountRestrictions, so they cannot drift apart, and the fourth is a middleware on the whole web group rather than something an edition remembers to add per route.

What the person sees

The sentence around the reason is the kit's and is translated. The reason itself is not: it is free text an administrator typed, in whatever language they typed it in, and running it through a translator would either lose it or invent a key per incident.

Your account has been suspended: Chargeback fraud
Tu cuenta ha sido suspendida: Chargeback fraud

A temporary suspension names its end instead:

Your account is suspended until 14 Mar 2026, 09:00.

Temporary suspensions end by themselves

expires_at in the past is a suspension that has lapsed, and the row is inert rather than wrong. Nothing has to sweep it up, and no scheduler has to be running for somebody to get their account back on time. Leave expires_at empty for "until somebody lifts it".

One suspension per account

account_bans has a unique key on user_id. Two rows would be two answers to "is this account barred", and lifting one of them would leave the door shut with nothing on screen to say so. Suspending an already suspended account edits the standing decision; lifting one deletes the row.

The history is not lost by that. Every ban and every lift is written to admin_actions with who did it, to whom, the reason and the end date, which is the table built to keep it.

Doing it from code

php
use Laraspring\Admin\Support\Bans;

Bans::apply($user, $admin, reason: 'Chargeback fraud', until: now()->addWeek());
Bans::lift($user, $admin);
Bans::current($user); // the AccountBan in force, or null

Use these rather than writing the row yourself: they are the one place the suspension and its audit line are written together, precisely so that they cannot come apart.

What a suspension does not do

  • It does not delete anything, cancel a subscription or remove somebody from an organization. It closes the door and nothing else.
  • It does not apply to administrators. The panel refuses to suspend one, for the same reason it refuses to impersonate one: a panel where one administrator can remove another is an escalation ladder.
  • It does not stop background work. A queued job about that user still runs; if that matters to your product, gate the job on the contract.

Laraspring is a commercial starter kit. Buying it gets you the source.