Skip to content

[Security] More radically remove Testing/HTTP Authentication contents #13523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 7 additions & 138 deletions testing/http_authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,143 +6,12 @@ How to Simulate HTTP Authentication in a Functional Test

.. caution::

Starting from Symfony 5.1, the methods explained in this article are no
longer recommended to logging in users in your tests. Instead, use
:ref:`the loginUser() method <testing_logging_in_users>`.
Starting from Symfony 5.1, a ``loginUser()`` method was introduced to
ease testing secured applications. See :ref:`testing_logging_in_users`
for more information about this.

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.
If you are still using an older version of Symfony, view
`previous versions of this article`_ for information on how to simulate
HTTP authentication.

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

<!-- config/packages/test/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://symfony.com/schema/dic/security"
xsi:schemaLocation="http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd
http://symfony.com/schema/dic/security
https://symfony.com/schema/dic/security/security-1.0.xsd">

<security:config>
<!-- replace 'main' by the name of your own firewall -->
<security:firewall name="main">
<security:http-basic/>
</security:firewall>
</security:config>

.. 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');

// somehow fetch the user (e.g. using the user repository)
$user = ...;

$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($user, null, $firewallName, $user->getRoles());
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();

$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
}
.. _previous versions of this article: https://symfony.com/doc/5.0/testing/http_authentication.html