L LAB

The ApiKeyService API

ApiKeyService is the single public entry point, bound as a singleton. Resolve it with app(ApiKeyService::class) or inject it.

use Mrsuner\ApiKeys\Services\ApiKeyService;

$service = app(ApiKeyService::class);

create

$created = $service->create($user, [
    'name'        => 'Production server',
    'environment' => 'live',                          // default 'live'
    'abilities'   => ['read:orders', 'write:orders'], // null = superkey
    'rate_limit'  => ['max_attempts' => 1000, 'decay_seconds' => 3600],
    'expires_at'  => now()->addYear(),                // null = never
]);

Returns a CreatedKey value object:

final class CreatedKey
{
    public string $plaintext;  // sk_live_… — show ONCE, never stored
    public ApiKey $model;      // the persisted row
}

The plaintext is the only time the full key exists in cleartext. Only its SHA-256 hash is stored. create() emits an api_key.created audit event and throws InvalidArgumentException when environment is not in the configured list.

resolve

$key = $service->resolve($bearer);   // ?ApiKey

Parses the prefix and environment, hashes the token segment, and looks it up by the unique key_hash index — a single indexed read with no side effects. Returns null for an unknown key or a malformed string. Usability is the caller’s decision via ApiKey::isUsable(); the api.key middleware does this for you.

revoke

$service->revoke($key, reason: 'leaked');

Sets is_active = false and revoked_at = now(), taking effect within the same request. Emits api_key.revoked (the reason is recorded in metadata; the hash never is).

rotate

// Immediate: the old key dies at once.
$new = $service->rotate($key);

// Overlap: the old key stays valid until the window closes.
$new = $service->rotate($key, overlapUntil: now()->addHour());

rotate() creates a new key inheriting the old one’s name, environment, abilities, rate limit and expiry, then either revokes the old key immediately or sets its revoked_at to the future overlap timestamp (keeping it usable until then). Returns the new CreatedKey and emits api_key.rotated.

The overlap window lets clients migrate to a new key with zero downtime: deploy the new key, let traffic drain off the old one, and it self-revokes when the window passes — no second deploy needed.

Audit events

EventTrigger
api_key.createdNew key generated
api_key.revokedKey revoked (user or service)
api_key.rotatedKey rotated
api_key.updatedKey metadata updated
api_key.admin_revokedAdmin revoked a user’s key
api_key.expiredPruning command found expired

Every event carries key_prefix in metadata. The key_hash and plaintext key are never logged. Events are emitted through the host’s audit_log() helper when present, and are a no-op otherwise.

Continue with User API & tracking.