Extending the model
composer require langsys/laravel-api-keys Point api-keys.model at your own subclass to add columns, relations, or application-specific authorization:
'model' => App\Models\ApiKey::class,Three protected hooks are the supported extension points:
class ApiKey extends \Langsys\ApiKeys\Models\ApiKey{ // Extra write authorization — a signed grant, device attestation, and so // on. OR'd into the type decision, so returning true lets a key write // where its type alone would not. This bypasses the read/write guarantee // by design; keep it as strict as the check it replaces. protected function extraWriteAllowances(Request $request): bool { return $this->hasValidWriteGrant($request); }
// Allow-list entries contributed by the application rather than stored on // the key — e.g. the egress addresses of a trusted internal service. protected function additionalAllowlistEntries(): array { return config('services.renderer.egress_ips', []); }
// Where the client address comes from, when $request->ip() is not it. protected function clientIp(Request $request): ?string { return $request->header('CF-Connecting-IP') ?: $request->ip(); }}Things that bite when subclassing
Section titled “Things that bite when subclassing”Every one of these has caught a real integration:
$fillable,$hiddenand$castsreplace rather than merge. In particular keep'type' => ApiKeyType::classin$casts— without it the middleware’s type check silently denies every write. Keepkey_hashin$hiddenor you will serialize hashes. If you must keep your own enum fortype, cast to it and setenforce_read_write => falseso the package stops making that decision at all.getByKey()does not filter byactive. That is deliberate — the middleware checksactiveseparately, and callers may want to distinguish “unknown key” from “revoked key”. But if you callgetByKey()yourself as an authentication path, checkactivetoo, or override it.isValidKey()already does both.- The package registers its model hooks in
booted(), notboot(). Key generation, thetype/activedefaults, default permissions and the lifecycle events all live there.Model::boot()only boots traits;booted()is invoked separately bybootIfNotBooted(). So a test helper that flushes model event listeners and then callsboot()to restore them will not restore these, and the failure is silent — the row saves, it just has nokey_hash. If you flush listeners, re-runbootIfNotBooted()or reset the model’s booted state. - If you override
booted()yourself, callparent::booted(). Eloquent does not chain it for you, and skipping it loses everything in the first bullet’s list at once.