What we need to do? We need to have secured page which will show some data and require user authentication:
Let`s split this task to few smaller tasks:
- Start new project
- Create page (homepage).
- Fill Homepage with some very secured content.
- Protect it with access policy
- Add login form
-
Install Symfony installer
- curl -sS https://get.symfony.com/cli/installer | bash
- mv ~/.symfony/bin/symfony /usr/local/bin/symfony
-
Create project
- symfony new tdd --full
-
Open Project with PHPStorm
-
Setup PHPStorm PHP and PHPUnit Configuration
-
(optional) Init git repository git init git remote add origin [email protected]:zzz/zzz.git
-
(optional) Start New Branch with name "login"
Create DefaultControllerTest.php inside of "tests/Controller" dirrectory. Extend it from WebTestCase class.
namespace tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DefaultControllerTest extends WebTestCase
{
private $client = null;
public function setUp()
{
$this->client = static::createClient();
}
}
Add homepage test function:
public function testHomePageIsAvailable() {
$this->client->request('GET', '/');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
Failed asserting that 404 matches expected 200.
Expected :200
Actual :404
Add DefaultController with "" route
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController
{
/**
* Homepage
*
* @Route("/", name="home", methods={"GET"})
* @return Response
*/
public function homeAction() {
return $this->render('home.html.twig');
}
}
Add home.html.twig to templates
{% extends 'base.html.twig' %}
{% block body %}
hello
{% endblock %}
OK (1 test, 1 assertion)
Add test function to DefaultControllerTest.php:
public function testHomePageContainsSomeContent() {
$this->client->request('GET', '/');
$this->assertContains('Very Secure data', $this->client->getResponse()->getContent());
}
Failed asserting that '<!DOCTYPE html>\n
<html>\n
<head>\n
<meta charset="UTF-8">\n
<title>Welcome!</title>\n
</head>\n
<body>\n
hello\n
</body>\n
</html>\n
' contains "Very Secured data".
Add content to home.html.twig:
{% extends 'base.html.twig' %}
{% block body %}
Very Secured data
{% endblock %}
OK (1 test, 1 assertion)
OK (2 tests, 2 assertions)
assertContains can be replaced by assertSame with crowler get element text
Modify testHomePageIsAvailable to have 401 by default
public function testHomePageIsAvailable() {
$this->client->request('GET', '/');
$this->assertEquals(401, $this->client->getResponse()->getStatusCode());
}
Failed asserting that 200 matches expected 401.
Add rule to access controll
- { path: ^/, roles: ROLE_ADMIN }
OK (1 test, 1 assertion)
Tests: 2, Assertions: 2, Failures: 1.
Add testHomePageIsAvailableWithGoodCredentials test
public function testHomePageIsAvailableWithGoodCredentials() {
$this->login();
$this->client->request('GET', '/');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
private function logIn()
{
$session = $this->client->getContainer()->get('session');
$firewallName = 'main';
$firewallContext = 'main';
$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);
}
public function testLoginForm() {
$this->client->request('GET', '/login');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
}
Failed asserting that 404 matches expected 200.
Add password encoder and InMemory users
encoders:
Symfony\Component\Security\Core\User\User: 'auto'
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
in_memory:
memory:
users:
john_admin: { password: '$argon2id$v=19$m=65536,t=4,p=1$w7EtYjXVZV/wXDBuogsF1w$vR4MMumgpX4FqWTlMnJJnTfG6+unZSSVhZazMsrOqBc', roles: ['ROLE_ADMIN'] }
jane_admin: { password: '$argon2id$v=19$m=65536,t=4,p=1$kMNjXkDDcYNScHG7W8MA3w$0V6OS2FwryNdLLOOBszAGC9cYt++JCHwJwc9gRfEVzk', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] }
Add access control rule:
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_ADMIN }
Generate login form (use Symfony\Component\Security\Core\User\User as User Entity)
bin/console make:auth
OK (1 test, 1 assertion)
Failed testHomePageIsAvailable
Modify testHomePageIsAvailable to have 302 redirect by default
public function testHomePageIsAvailable() {
$this->client->request('GET', '/');
$this->assertEquals(302, $this->client->getResponse()->getStatusCode());
}
public function testLoginFormSubmit() {
$this->client->request('POST', '/login', [
'username' => 'john_admin',
'password' => 'test',
'_csrf_token' => $this->client->getContainer()->get('security.csrf.token_manager')->getToken('authenticate')->getValue(),
]);
$this->assertEquals(302, $this->client->getResponse()->getStatusCode());
$this->assertEquals('/', $this->client->getResponse()->headers->get('location')); //success we are inside of secured zone
}
public function testLoginFormSubmitWithWrongPassword() {
$this->client->request('POST', '/login', [
'username' => 'john_admin',
'password' => '1',
'_csrf_token' => $this->client->getContainer()->get('security.csrf.token_manager')->getToken('authenticate')->getValue(),
]);
$this->assertEquals(302, $this->client->getResponse()->getStatusCode());
$this->assertEquals('/login', $this->client->getResponse()->headers->get('location')); //fail we still on login page
}
public function testLoginFormAlreadyAuthorized() {
$this->login();
$this->client->request('GET', '/login');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$this->assertEquals(null, $this->client->getResponse()->headers->get('location')); //redirect to homepage
}
public function testLoginFormProcess() {
$this->logIn();
$this->client->request('GET', '/');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode()); // we are still authenticated
$this->logOut();
$this->client->request('GET', '/');
$this->assertEquals(302, $this->client->getResponse()->getStatusCode()); // we are no more authenticated
}
public function testLoginFormSubmitWithWrongPassword() {
$this->client->request('POST', '/login', [
'username' => 'john_admin',
'password' => '1',
'_csrf_token' => $this->client->getContainer()->get('security.csrf.token_manager')->getToken('authenticate')->getValue(),
]);
$this->assertEquals(302, $this->client->getResponse()->getStatusCode());
$this->assertEquals('/login', $this->client->getResponse()->headers->get('location')); //fail, we still on login page
}
private function logIn()
{
$this->client->request('POST', '/login', [
'username' => 'john_admin',
'password' => 'test',
'_csrf_token' => $this->client->getContainer()->get('security.csrf.token_manager')->getToken('authenticate')->getValue(),
]);
$this->assertEquals(302, $this->client->getResponse()->getStatusCode());
$this->assertEquals('/', $this->client->getResponse()->headers->get('location')); //success, we are inside of secured zone
}
private function logOut()
{
$this->client->request('GET', '/logout');
$this->assertEquals(302, $this->client->getResponse()->getStatusCode());
$this->assertEquals('http://localhost/', $this->client->getResponse()->headers->get('location')); //redirect to homepage
}