> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sinemacula.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Enforce Active State on Identities and Principals

> Use the CanBeActive contract to block suspended or banned users on every authentication path without relying on short token lifetimes.

Access tokens are self-contained: once issued, they remain cryptographically valid until they expire. If you suspend a user after issuing their token, the guard has no way to know about the suspension unless you give it a live signal. The `CanBeActive` contract provides exactly that — a per-request active-state check that both guards consult on every authentication attempt, with no dependency on token expiry or session invalidation.

## What `CanBeActive` does

`CanBeActive` is an optional capability contract that you implement on your identity model. When both guards — the JWT bearer guard and the HTTP Basic guard — resolve an identity, they check whether the model implements `CanBeActive`. If it does, they call `isActive()` and reject authentication immediately when it returns `false`.

The check runs on every request, regardless of whether resolution caching is enabled. Caching only short-circuits the database lookup for the identity row itself; the active-state check always runs against the in-memory model returned by that lookup.

## Why use it

Without `CanBeActive`, the only way to block a compromised or suspended account is to wait for all outstanding access tokens to expire. If your `access_ttl_minutes` is set to 60 minutes, a suspended user retains access for up to an hour after suspension.

With `CanBeActive`, the guard rejects the request as soon as the check fails, regardless of how long the token has left to live. This is the preferred approach for:

* **Suspended accounts** — a user flagged by a support workflow
* **Banned accounts** — a user blocked for policy violations
* **Soft-deleted accounts** — a user whose record is deleted but whose token is still in circulation
* **Administratively locked accounts** — a user locked pending a security review

## Implementation

Implement `CanBeActive` on your identity model and return a boolean from `isActive()`:

```php theme={null}
use SineMacula\Laravel\Authentication\Contracts\CanBeActive;
use SineMacula\Laravel\Authentication\Contracts\Identity;
use SineMacula\Laravel\Authentication\Contracts\Principal;
use SineMacula\Laravel\Authentication\Traits\ActsAsPrincipal;
use SineMacula\Laravel\Authentication\Traits\Authenticatable;
use Illuminate\Foundation\Auth\User;

class AppUser extends User implements Identity, Principal, CanBeActive
{
    use Authenticatable, ActsAsPrincipal;

    public function isActive(): bool
    {
        return $this->suspended_at === null;
    }
}
```

No registration is required beyond implementing the interface. Both guards detect the contract via `instanceof` on the resolved identity.

<Tip>
  Combine `CanBeActive` with Laravel's soft deletes by checking both `suspended_at` and `deleted_at` in the same method: `return $this->suspended_at === null && $this->deleted_at === null;`. If your model uses `SoftDeletes`, queries that scope to non-deleted records will already exclude soft-deleted users from the provider lookup — `isActive()` adds a second line of defence at the guard level in case the record is loaded through a non-scoped query path.
</Tip>

## Blocking inactive principals

The active-state check is not limited to identities. The guard also checks the resolved principal: if the principal implements `CanBeActive` and its `isActive()` returns `false`, authentication fails at the principal resolution step.

This matters in 3D adoption mode, where the principal is a separate model (such as a tenant membership) that can be independently suspended. A user whose identity is active but whose membership in a particular tenant has been revoked is blocked from acting as that principal, even if their identity token is otherwise valid.

## What happens on failure

When `isActive()` returns `false` for either the identity or the principal, the guard fires Laravel's standard `Illuminate\Auth\Events\Failed` event and returns `null` from `Auth::user()`. The request surface is a standard 401 response — no information about the reason for rejection is leaked to the caller.

## Active-state and the resolution cache

The resolution cache, when enabled, only short-circuits the identity provider lookup (the database query). The active-state check runs against the model that the provider returns — cached or not — on every request. You cannot disable or bypass the `isActive()` call by enabling caching.

Similarly, device validation and principal resolution always run live. There is no configuration path that causes the guard to skip the active-state check.
