chrisbjr/api-guard
API Guard is a Laravel package for API key authentication. It lets you generate and manage API keys, protect routes with middleware, and authenticate requests via configurable headers or query parameters, with hashed key storage and easy integration.
1. Install (and mind the version). chrisbjr/api-guard was last released in 2016, so it targets Laravel 5.x. If you're on a modern Laravel, expect either a fork or a --ignore-platform-reqs install plus manual fixes; on Laravel ≤5.4 you'll also need to register the service provider in config/app.php.
composer require chrisbjr/api-guard
php artisan vendor:publish --provider="Chrisbjr\ApiGuard\Providers\ApiGuardServiceProvider"
php artisan migrate
2. Register the middleware alias in app/Http/Kernel.php (check the package README for the exact class name in your version):
protected $routeMiddleware = [
// ...
'auth.apikey' => \Chrisbjr\ApiGuard\Http\Middleware\AuthenticateApiKey::class,
];
3. Look at the two files that matter. config/apiguard.php (header name, query-string fallback, model binding, ignored routes) and the published migration for api_keys. Everything else in the package is a thin middleware around those two.
4. First use case — protect a route group and issue a key.
Route::group(['prefix' => 'api/v1', 'middleware' => 'auth.apikey'], function () {
Route::get('/orders', 'Api\OrderController@index');
});
use Chrisbjr\ApiGuard\Models\ApiKey;
$apiKey = ApiKey::generate($user->id); // persists the row
return response()->json(['key' => $apiKey->key]);
curl -H "X-Api-Key: <key>" https://app.test/api/v1/orders
If the key is bound to a user, the middleware logs that user in, so auth()->user() / $request->user() work normally inside your controller.
Issue keys as part of registration, not by hand. Generate on signup (or on "create API credential" in a settings page) and return the plaintext key exactly once:
$apiKey = ApiKey::generate($user->id);
return response()->json(['api_key' => $apiKey->key], 201);
Because the model is polymorphic (user_id + user_type), the same pattern works for teams, tenants, or service accounts.
Multiple keys per consumer. One key per device or per environment (staging/prod) makes revocation surgical. Store a human label alongside the key.
Revocation is a row delete. There's no caching or token introspection indirection, so $apiKey->delete() is effective on the next request. This is the package's biggest operational strength — no blacklist logic to maintain.
Use the query-string fallback only for clients that can't set headers. The config exposes a query_key (default api_key) for webhooks and legacy integrations. Prefer the header path everywhere you control the client.
Combine with throttling. The middleware does a database lookup per request; put throttle: in front of it so unauthenticated floods don't hit the api_keys table:
Route::group(['middleware' => ['throttle:60,1', 'auth.apikey']], function () { ... });
Whitelist noisy endpoints. Health checks, Stripe/ GitHub webhooks, and docs should skip the key check via the ignore list in config/apiguard.php rather than by splitting route files.
Testing. The middleware is a normal route middleware, so it runs in feature tests:
$key = ApiKey::generate($user->id);
$this->withHeader('X-Api-Key', $key->key)
->getJson('/api/v1/orders')
->assertOk();
$this->getJson('/api/v1/orders')->assertStatus(401);
Integration with existing auth. Treat API keys as an authentication layer and keep your authorization in policies/gates — the middleware only proves "who", not "what".
str_random, container bindings, Route:: signatures), and plan an exit. For new projects, Laravel Sanctum or a small custom middleware is a better bet.last_used_at/revoked_at column and never log the X-Api-Key header (many log formatters and APMs capture headers by default).Referer. If you must use query_key, scrub it in your log middleware.OPTIONS requests carry no X-Api-Key. Add OPTIONS handling (or the route) to the ignore list, otherwise browsers break before the real call.X-Api-Key survives your stack (nginx underscores_in_headers is the classic trap if you rename it).ApiKey, point the config at your own model — this is the cleanest extension point, and the one to use when migrating off the package (dual-read old keys, issue new ones, sunset).ApiKey::where('key', $k)->exists()), is the header name in config the same one your client sends, and does the route appear in the ignore list unexpectedly?api_keys rows, email/notify consumers, then swap the middleware alias globally — because every protected route already uses one alias, the cutover is a one-line change in Kernel.php.How can I help you explore Laravel packages today?