birdcar/authkit-laravel
birdcar/authkit-laravel is a Laravel authentication toolkit designed to simplify common auth setup and user flows. It offers a lightweight starting point for adding authentication to Laravel applications, with a small footprint, straightforward integration, and a minimal approach for projects seekin...
Start with the config and the guard, then wire a test on day one — this release makes the test loop the fastest way to learn the package.
Install and publish config
composer require birdcar/authkit-laravel
php artisan vendor:publish --tag="authkit-config"
Set the WorkOS credentials (WORKOS_CLIENT_ID, WORKOS_API_KEY, redirect URI) and point the workos guard at your User model.
Look first at: config/authkit.php (guard, organization sync, FGA, Vault disk, HTTP transport), then docs/testing.md — it is the canonical map of every fake and assertion added in 2.1.0.
First use case — gate checks driven by WorkOS roles/permissions:
Route::middleware(['auth:workos'])->group(function () {
Route::get('/dashboard', DashboardController::class)
->middleware('can:view-dashboard');
});
Inside the request, use Authkit::currentOrganization() and $request->organization() rather than re-reading the session.
First test — no cookies, no JWKS fetch, no network:
use Birdcar\Authkit\Facades\Authkit;
it('shows the dashboard', function () {
Authkit::actingAs(User::factory()->create(), [
'organization' => 'org_123',
'roles' => ['admin'],
'permissions' => ['dashboard.view'],
]);
$this->get('/dashboard')->assertOk();
});
If you already have a test suite, add Http::preventStrayRequests() to your base TestCase before adopting the fakes — it will surface any code path still talking to WorkOS for real.
Synthetic sessions for feature tests. Authkit::actingAs($user, [...]) builds a session that flows through the same contracts as a real login, so anything downstream of the guard behaves normally:
Authkit::actingAs($user, [
'organization' => $organization, // model or 'org_...' string
'roles' => ['admin'],
'permissions' => ['invoices.manage'],
'features' => ['beta-reports' => true],
]);
Feature flags land in the Pennant workos store, so Feature::active('beta-reports') and Blade @feature directives work unchanged. Authkit::actingAsGuest() covers the unauthenticated branch.
Impersonation. Pass the impersonator in the acting-as options; the Impersonating event fires so your listeners and audit hooks still run.
Selective faking. Fake the whole surface, or only what the test touches:
$workos = Authkit::fake(); // everything
$workos = Authkit::fake(['fga', 'vault']); // just these two
Unfaked managers keep real behavior on every code path — useful when a test needs the genuine Vault crypto or a live SDK call, dangerous if you forget preventStrayRequests().
Scripting and asserting per manager.
assertChecked(...) to prove the check happened, not just that the response was right.AuditLog::assertLogged(...) resolves the real actor and applies the same metadata sanitization as production; export lifecycle and schema registrations are covered too.completeSync() to drive the job to completion without queue workers.authkit-key guard, so key middleware is genuinely exercised.ConflictException parity — the Vaulted cast and vault disk run their real code over marked fake crypto.HTTP-level assertions. WorkOS traffic now rides the application's HTTP client (authkit.http.transport, default laravel), so Laravel's idioms apply:
Http::fake([
'api.workos.com/*' => Http::response(['data' => []], 200),
]);
Authkit::actingAs($user);
$this->get('/invitations')->assertOk();
Http::assertSent(fn ($request) => str_contains($request->url(), 'invitations'));
This is the right tool for asserting wire shape (endpoints, headers, payloads); the manager fakes are the right tool for asserting application behavior.
Test-suite composition. Use actingAs for the 90% case (controllers, policies, gates), manager fakes for integration tests around a single WorkOS feature, and Http::fake() only when the request itself is the contract.
Authkit::fake(['fga']) leaves every other manager live. If a controller touches Vault, the test hits the network. Pair partial faking with Http::preventStrayRequests() so leaks fail loudly instead of flakily.AuditLog::assertLogged(...) before Authkit::fake() raises a LogicException naming the fix. Good behavior, but it means a shared test helper that fakes one manager can't be reused to assert on another.RefreshDatabase boundaries. Re-script inside each test — don't stash state on a static or a setUp singleton.actingAs is not a real login. No cookie, no JWKS fetch, no SDK call. Anything keyed off actual authentication events (beyond Impersonating) won't fire. If your test asserts a Login listener, drive the real guard instead.org_... strings both work, but the resolved claim differs — assert on Authkit::currentOrganization()->id rather than the raw object when you care about shape.ConflictException behavior; treat the ciphertext as opaque.AUTHKIT_HTTP_TRANSPORT=guzzle opts out of Laravel's HTTP client, which means Http::fake() and Http::assertSent() no longer see WorkOS calls. Only do this if you need Guzzle-specific behavior; the SDK's retry, error mapping, and JWKS caching are unchanged either way.final. That's a designed seam for the testing subclasses and is guarded by an arch test — but don't treat it as an invitation to extend VaultManager in app code. Prefer the fakes; subclassing ties you to internals.workos/workos-php 9.2.0 changed the events poller signature. The package now passes the $events filter explicitly (empty = all types, same wire request). If you wrap or override the poller yourself, mirror that call or you'll break on the version bump.Http::preventStrayRequests() on? (3) with Http::fake(), dump Http::recorded() to confirm the request was even attempted — a LogicException or an empty sent-list usually means the guard short-circuited before any WorkOS call.How can I help you explore Laravel packages today?