smartassert/test-authenticaton-provider-bundle
A Symfony bundle from SmartAssert that provides a test authentication provider, letting functional and integration tests sign in users through configurable test credentials without a real identity backend.
The smartassert/test-authenticaton-provider-bundle is a small Symfony bundle whose job is to make the security layer disappear during functional tests: instead of driving real login forms, JWT exchanges, or session bootstrapping, you register a test user provider, point a firewall at it, and authenticate as an arbitrary user in one line. Its only real use case is a Symfony app that has functional tests (WebTestCase / KernelBrowser) and needs to hit authenticated endpoints quickly.
Minimal steps:
Install in require-dev, not require:
composer require --dev smartassert/test-authenticaton-provider-bundle
(Note the typo in the package name — authenticaton, not authentication. The repo is TestAuthenticationProviderBundle, but the Packagist name drops the second i. This is a common source of "package not found" confusion; see Gotchas.)
Register the bundle for the test env only in config/bundles.php:
return [
// ...
SmartAssert\TestAuthenticationProviderBundle\TestAuthenticationProviderBundle::class
=> ['test' => true],
];
Add a dedicated firewall in config/packages/security.yaml, again scoped to test via when@test:
when@test:
security:
providers:
test_users:
id: smartassert.test_authentication_provider
firewalls:
test:
pattern: ^/
provider: test_users
stateless: true
custom_authenticators: [] # no-op, the provider trusts the session
Look first at the bundle's DependencyInjection/ directory in the source — it's a tiny bundle, so the service ID (smartassert.test_authentication_provider and friends) and the config tree are the most authoritative docs you'll get. Stars are 0 and dependents are 0, so there is effectively no blog post to copy from.
First use case: authenticate as a user inside a WebTestCase:
public function testDashboard(): void
{
$user = $this->getTestProvider()->create('[email protected]', ['ROLE_USER']);
$client = static::createClient();
$client->loginUser($user);
$client->request('GET', '/dashboard');
self::assertResponseIsSuccessful();
}
That single loginUser($user) replaces an entire login flow and is the 90% case.
Per-test user creation, not fixtures. The provider is essentially an in-memory, programmatic user store. The idiomatic pattern is to create the user you need inside the test, immediately before the request, rather than seeding a fixture. It keeps tests readable and prevents cross-test state bleed.
Named test users for role coverage. Where a fixture loader would normally create admin, editor, viewer, create them inline and pass them to loginUser():
#[DataProvider('roleProvider')]
public function testAuthorization(string $email, array $roles, int $expected): void
{
$user = $this->getTestProvider()->create($email, $roles);
$client = static::createClient();
$client->loginUser($user);
$client->request('GET', '/admin');
self::assertResponseStatusCodeSame($expected);
}
A base WebTestCase trait. Because the provider is a shared service, the natural extension point is a trait that (a) fetches the provider from the test container, (b) tracks users created so far, and (c) clears them in tearDown(). Even though the provider is rebuilt per kernel boot, Symfony reuses the kernel across tests in a class by default — which is exactly when leakage bites.
Integration with KernelBrowser::loginUser(). This is the seam that makes the bundle worthwhile: loginUser() accepts anything implementing Symfony\Component\Security\Core\User\UserInterface, so as long as the provider returns a normal user object, no extra glue is needed. There is no need to hand-craft a session or a Security token.
Pair with zenstruck/browser or Symfony's assertResponse* helpers. The bundle has no assertion helpers of its own; it deliberately only solves "who am I", leaving "what should happen" to the standard testing toolkit. Don't expect a fluent API.
Scoping by firewall. If your app has multiple firewalls (e.g. api with JWT and main with form login), the cleanest pattern is to add the test provider only to the firewall your functional tests actually hit, and to override the real provider under when@test:. This keeps the production security.yaml untouched and makes the diff obviously test-only.
CI note. Because the bundle is dev-only and versioned via when@test, it is safe to commit to composer.json in require-dev; no composer install --no-dev leakage.
The package name typo is real. The Packagist vendor/name is smartassert/test-authenticaton-provider-bundle (missing i in "authentication") while the GitHub repo is TestAuthenticationProviderBundle. composer require smartassert/test-authentication-provider-bundle will 404. Search Packagist by vendor, not by the repo name.
Never let the bundle activate outside test. Two independent guards are wise: ['test' => true] in bundles.php and when@test: around the firewall. A single guard that gets removed during a config refactor is how a "log in as anyone" backdoor reaches staging.
loginUser() and stateless firewalls interact oddly. loginUser() uses the session by default; if you declare the test firewall stateless: true, the user is injected per-request via a token but persistence semantics differ. If you see 401s despite loginUser(), first check the firewall's stateless flag and whether the route sits behind a different firewall than the one you configured.
The provider is a service, so it is stateful within a kernel boot. Symfony's KernelBrowser reboots the kernel between requests by default, but does not necessarily do so between tests in the same class. If user A "leaks" into test B, the fix is either $client->disableReboot() + explicit teardown, or clearing the provider in tearDown(). Prefer the latter.
Debug the wiring, not the code. When authentication silently "doesn't take", run:
php bin/console debug:container smartassert
php bin/console debug:config security
Confirm the provider service ID matches what you put under providers:, and that the firewall pattern actually matches the URL you're requesting (pattern: ^/ is easy to shadow with an earlier dev firewall).
Version constraints are your friend. With 0 stars and 0 dependents, there is no ecosystem testing this against your Symfony version. Pin the major and read the bundle's composer.json for the symfony/security-bundle constraint before upgrading Symfony majors — the UserProviderInterface signature has changed historically (loadUserByIdentifier vs loadUserByUsername), and a bundle this small is unlikely to maintain compatibility shims.
Debug output is minimal by design. There is no logger integration and no test-mode banner. If you need to know which user the request ran as, add a tiny event subscriber in your own tests/ support code rather than expecting the bundle to tell you.
It doesn't replace authorization testing. The provider gets you authenticated; it does not test your voters, role hierarchy, or firewall access rules. Tests that "pass" with a superuser created by this bundle can mask real 403s — deliberately test with a user that has insufficient roles to prove the denial path works.
Extension point to lean on: your own service decorator. If you need richer behavior (factories, role presets, users with custom fields), decorate the bundle's provider service in your test service config. This is the intended way to grow the bundle's surface without forking it — which, given the project's size, you should avoid.
Given 0 dependents and a very recent release (2025-12-01), treat it as alpha-quality for your purposes. Budget an afternoon to read the whole src/ tree before you build a test suite on top of it. It will be short. If it looks under-maintained for your needs, the same effect is achievable in ~40 lines of your own UserProviderInterface implementation plus a when@test firewall — an entirely reasonable fallback.
How can I help you explore Laravel packages today?