diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml index 2d698b77c20..d74f53ad9e4 100644 --- a/.doctor-rst.yaml +++ b/.doctor-rst.yaml @@ -85,3 +85,4 @@ whitelist: - ".. _`Deploying Symfony 4 Apps on Heroku`: https://devcenter.heroku.com/articles/deploying-symfony4" - "// 224, 165, 141, 224, 164, 164, 224, 165, 135])" - '.. versionadded:: 0.2' # MercureBundle + - 'provides a ``loginUser()`` method to simulate logging in in your functional' diff --git a/_build/redirection_map b/_build/redirection_map index e446f199ff9..beced7cb3b7 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -480,3 +480,4 @@ /components/translation/custom_formats https://github.com/symfony/translation /components/translation/custom_message_formatter https://github.com/symfony/translation /components/notifier https://github.com/symfony/notifier +/testing/http_authentication /testing diff --git a/security.rst b/security.rst index eeec2223388..8b48f431516 100644 --- a/security.rst +++ b/security.rst @@ -172,6 +172,8 @@ Now that Symfony knows *how* you want to encode the passwords, you can use the ``UserPasswordEncoderInterface`` service to do this before saving your users to the database. +.. _user-data-fixture: + For example, by using :ref:`DoctrineFixturesBundle `, you can create dummy database users: diff --git a/testing.rst b/testing.rst index bd3030df5c8..7f6f18f2261 100644 --- a/testing.rst +++ b/testing.rst @@ -567,6 +567,62 @@ command. If the information you need to check is available from the profiler, use it instead. +Logging in Users (Authentication) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 5.1 + + The ``loginUser()`` method was introduced in Symfony 5.1. + +When you want to add functional tests for protected pages, you have to +first "login" as a user. Reproducing the actual steps - such as +submitting a login form - make a test very slow. For this reason, Symfony +provides a ``loginUser()`` method to simulate logging in in your functional +tests. + +Instead of login in with real users, it's recommended to create a user only for +tests. You can do that with Doctrine :ref:`data fixtures `, +to load the testing users only in the test database. + +After loading users in your database, use your user repository to fetch +this user and use +:method:`$client->loginUser() ` +to simulate a login request:: + + // tests/Controller/ProfileControllerTest.php + namespace App\Tests\Controller; + + use App\Repository\UserRepository; + use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + + class ProfileControllerTest extends WebTestCase + { + // ... + + public function testVisitingWhileLoggedIn() + { + $client = static::createClient(); + $userRepository = static::$container->get(UserRepository::class); + + // retrieve the test user + $testUser = $userRepository->findOneByEmail('john.doe@example.com'); + + // simulate $testUser being logged in + $client->loginUser($testUser); + + // test e.g. the profile page + $client->request('GET', '/profile'); + $this->assertResponseIsSuccessful(); + $this->assertSelectorTextContains('h1', 'Hello John!'); + } + } + +You can pass any +:class:`Symfony\\Component\\Security\\Core\\User\\UserInterface` instance to +``loginUser()``. This method creates a special +:class:`Symfony\\Bundle\\FrameworkBundle\\Test\\TestBrowserToken` object and +stores in the session of the test client. + Accessing the Profiler Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/testing/http_authentication.rst b/testing/http_authentication.rst deleted file mode 100644 index 33bd113d544..00000000000 --- a/testing/http_authentication.rst +++ /dev/null @@ -1,130 +0,0 @@ -.. index:: - single: Tests; HTTP authentication - -How to Simulate HTTP Authentication in a Functional Test -======================================================== - -Authenticating requests in functional tests can slow down the entire test suite. -This could become an issue especially when the tests reproduce the same steps -that users follow to authenticate, such as submitting a login form or using -OAuth authentication services. - -This article explains the two most popular techniques to avoid these issues and -create fast tests when using authentication. - -Using a Faster Authentication Mechanism Only for Tests ------------------------------------------------------- - -When your application is using a ``form_login`` authentication, you can make -your tests faster by allowing them to use HTTP authentication. This way your -tests authenticate with the simple and fast HTTP Basic method whilst your real -users still log in via the normal login form. - -The trick is to use the ``http_basic`` authentication in your application -firewall, but only in the configuration file used by tests: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/test/security.yaml - security: - firewalls: - # replace 'main' by the name of your own firewall - main: - http_basic: ~ - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // config/packages/test/security.php - $container->loadFromExtension('security', [ - 'firewalls' => [ - // replace 'main' by the name of your own firewall - 'main' => [ - 'http_basic' => [], - ], - ], - ]); - -Tests can now authenticate via HTTP passing the username and password as server -variables using the second argument of ``createClient()``:: - - $client = static::createClient([], [ - 'PHP_AUTH_USER' => 'username', - 'PHP_AUTH_PW' => 'pa$$word', - ]); - -The username and password can also be passed on a per request basis:: - - $client->request('DELETE', '/post/12', [], [], [ - 'PHP_AUTH_USER' => 'username', - 'PHP_AUTH_PW' => 'pa$$word', - ]); - -Creating the Authentication Token ---------------------------------- - -If your application uses a more advanced authentication mechanism, you can't -use the previous trick, but it's still possible to make tests faster. The trick -now is to bypass the authentication process, create the *authentication token* -yourself and store it in the session. - -This technique requires some knowledge of the Security component internals, -but the following example shows a complete example that you can adapt to your -needs:: - - // tests/Controller/DefaultControllerTest.php - namespace App\Tests\Controller; - - use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; - use Symfony\Component\BrowserKit\Cookie; - use Symfony\Component\HttpFoundation\Response; - use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; - - class DefaultControllerTest extends WebTestCase - { - private $client = null; - - public function setUp() - { - $this->client = static::createClient(); - } - - public function testSecuredHello() - { - $this->logIn(); - $crawler = $this->client->request('GET', '/admin'); - - $this->assertSame(Response::HTTP_OK, $this->client->getResponse()->getStatusCode()); - $this->assertSame('Admin Dashboard', $crawler->filter('h1')->text()); - } - - private function logIn() - { - $session = self::$container->get('session'); - - $firewallName = 'secure_area'; - // if you don't define multiple connected firewalls, the context defaults to the firewall name - // See https://symfony.com/doc/current/reference/configuration/security.html#firewall-context - $firewallContext = 'secured_area'; - - // you may need to use a different token class depending on your application. - // for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken - $token = new UsernamePasswordToken('admin', null, $firewallName, ['ROLE_ADMIN']); - $session->set('_security_'.$firewallContext, serialize($token)); - $session->save(); - - $cookie = new Cookie($session->getName(), $session->getId()); - $this->client->getCookieJar()->set($cookie); - } - }