{"id":2259,"date":"2013-08-19T11:15:37","date_gmt":"2013-08-19T11:15:37","guid":{"rendered":"https:\/\/intelligentbee.com\/blog\/?p=2259"},"modified":"2024-09-30T07:41:51","modified_gmt":"2024-09-30T07:41:51","slug":"symfony2-jobeet-day-13-security","status":"publish","type":"post","link":"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-13-security\/","title":{"rendered":"Symfony2 Jobeet Day 13: Security"},"content":{"rendered":"<p><span style=\"font-family: timesnew roman; font-size: 12px;\">* This article is part of the original <a href=\"http:\/\/symfony.com\/legacy\/doc\/jobeet?orm=Doctrine\" target=\"_blank\" rel=\"noopener\">Jobeet Tutorial<\/a>, created by Fabien Potencier, for Symfony 1.4.<\/span><\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_68_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title \" >Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-13-security\/#Security_in_jobeet\" title=\"Security in jobeet\">Security in jobeet<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-13-security\/#Logout\" title=\"Logout\">Logout<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/intelligentbee.com\/blog\/symfony2-jobeet-day-13-security\/#The_User_Session\" title=\"The User Session\">The User Session<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Security_in_jobeet\"><\/span>Security in jobeet<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3>Securing the Application<\/h3>\n<p>Security is a two-step process whose goal is to prevent a user from accessing a resource that he\/she should not have access to. In the first step of the process, the <strong>authentication<\/strong>, the security system identifies who the user is by requiring the user to submit some sort of identification. Once the system knows who you are, the next step, called the <strong>authorization<\/strong>, is to determine if you should have access to a given resource (it checks to see if you have privileges to perform a certain action).<span id=\"more-190\"><\/span><\/p>\n<p>The security component can be configured via your application configuration using the <code>security.yml<\/code> file from the <code>app\/config<\/code> folder. To secure our application change your <code>security.yml<\/code> file:<\/p>\n<pre class=\"lang:yaml decode:true\" title=\"app\/config\/security.yml\">security:\r\n    role_hierarchy:\r\n        ROLE_ADMIN:       ROLE_USER\r\n        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]\r\n\r\n    firewalls:\r\n        dev:\r\n            pattern:  ^\/(_(profiler|wdt)|css|images|js)\/\r\n            security: false\r\n\r\n        secured_area:\r\n            pattern:    ^\/\r\n            anonymous: ~\r\n            form_login:\r\n                login_path:  \/login\r\n                check_path:  \/login_check\r\n                default_target_path: ibw_jobeet_homepage\r\n\r\n    access_control:\r\n        - { path: ^\/admin, roles: ROLE_ADMIN }\r\n\r\n    providers:\r\n        in_memory:\r\n            memory:\r\n                users:\r\n                    admin: { password: adminpass, roles: 'ROLE_ADMIN' }\r\n\r\n    encoders:\r\n        SymfonyComponentSecurityCoreUserUser: plaintext<\/pre>\n<p>This configuration will secure the <code>\/admin<\/code> section of the website (all urls that start with <code>\/admin<\/code>) and will allow only users with <code>ROLE_ADMIN<\/code> to access it (see the <code>access_control<\/code> section). In this example the admin user is defined in the configuration file (the <code>providers<\/code> section) and the password is not encoded (<code>encoders<\/code>).<\/p>\n<p>For authenticating users, a traditional login form will be used, but we need to implement it. First, create two routes: one that will display the <code>login<\/code> form (i.e. <code>\/login<\/code>) and one that will handle the <code>login<\/code> form submission (i.e. <code>\/login_check<\/code>):<\/p>\n<pre class=\"lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/routing.yml\">login:\r\n    pattern:   \/login\r\n    defaults:  { _controller: IbwJobeetBundle:Default:login }\r\nlogin_check:\r\n    pattern:   \/login_check\r\n\r\n# ...<\/pre>\n<p>&nbsp;<\/p>\n<p>We will not need to implement a controller for the \/login_check URL as the firewall will automatically catch and process any form submitted to this URL. But you need to create a route so that it can be used to generate the form submission URL in the login template below.<\/p>\n<p>Next, let\u2019s create the action that will display the login form:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Controller\/DefaultController.php\">namespace IbwJobeetBundleController;\r\n\r\nuse SymfonyBundleFrameworkBundleControllerController;\r\nuse SymfonyComponentSecurityCoreSecurityContext;\r\n\r\nclass DefaultController extends Controller\r\n{\r\n    \/\/ ...\r\n\r\n    public function loginAction()\r\n    {\r\n        $request = $this-&gt;getRequest();\r\n        $session = $request-&gt;getSession();\r\n\r\n        \/\/ get the login error if there is one\r\n        if ($request-&gt;attributes-&gt;has(SecurityContext::AUTHENTICATION_ERROR)) {\r\n            $error = $request-&gt;attributes-&gt;get(SecurityContext::AUTHENTICATION_ERROR);\r\n        } else {\r\n            $error = $session-&gt;get(SecurityContext::AUTHENTICATION_ERROR);\r\n            $session-&gt;remove(SecurityContext::AUTHENTICATION_ERROR);\r\n        }\r\n\r\n        return $this-&gt;render('IbwJobeetBundle:Default:login.html.twig', array(\r\n            \/\/ last username entered by the user\r\n            'last_username' =&gt; $session-&gt;get(SecurityContext::LAST_USERNAME),\r\n            'error'         =&gt; $error,\r\n        ));\r\n    }\r\n}<\/pre>\n<p>When the user submits the form, the security system automatically handles the form submission for you. If the user had submitted an invalid username or password, this action reads the form submission error from the security system so that it can be displayed back to the user. Your only job is to display the login form and any login errors that may have occurred, but the security system itself takes care of checking the submitted username and password and authenticating the user.<\/p>\n<p>Finally, let\u2019s create the corresponding template:<\/p>\n<pre class=\"lang:xhtml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/Default\/login.html.twig\">{% if error %}\r\n    &lt;div&gt;{{ error.message }}&lt;\/div&gt;\r\n{% endif %}\r\n\r\n&lt;form action=\"{{ path('login_check') }}\" method=\"post\"&gt;\r\n    &lt;label for=\"username\"&gt;Username:&lt;\/label&gt;\r\n    &lt;input type=\"text\" id=\"username\" name=\"_username\" value=\"{{ last_username }}\" \/&gt;\r\n\r\n    &lt;label for=\"password\"&gt;Password:&lt;\/label&gt;\r\n    &lt;input type=\"password\" id=\"password\" name=\"_password\" \/&gt;\r\n\r\n    &lt;button type=\"submit\"&gt;login&lt;\/button&gt;\r\n&lt;\/form&gt;<\/pre>\n<p>Now, if you try to access <code>http:\/\/jobeet.local\/app_dev.php\/admin\/dashboard<\/code> url, the login form will show and you will have to enter the username and password defined in <code>security.yml<\/code> (admin\/adminpass) to get to the admin section of Jobeet.<\/p>\n<h3>User Providers<\/h3>\n<p>During authentication, the user submits a set of credentials (usually a username and password). The job of the authentication system is to match those credentials against some pool of users. So where does this list of users come from?<\/p>\n<p>In Symfony2, users can come from anywhere \u2013 a configuration file, a database table, a web service, or anything else you can dream up. Anything that provides one or more users to the authentication system is known as a \u201cuser provider\u201d. Symfony2 comes standard with the two most common user providers: one that loads users from a configuration file and one that loads users from a database table.<\/p>\n<p>Above, we used the first case: specifying users in a configuration file.<\/p>\n<pre class=\"lang:yaml decode:true \" title=\"app\/config\/security.yml\"># ...\r\n\r\nproviders:\r\n    in_memory:\r\n        memory:\r\n            users:\r\n                admin: { password: adminpass, roles: 'ROLE_ADMIN' }\r\n\r\n# ...<\/pre>\n<p>But you will usually want the users to be stored in a database table. To do this we will add a new <code>user<\/code> table to our jobeet database. First let\u2019s create the orm for this new table:<\/p>\n<pre class=\"lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/doctrine\/User.orm.yml\">IbwJobeetBundleEntityUser:\r\n    type: entity\r\n    table: user\r\n    id:\r\n        id:\r\n            type: integer\r\n            generator: { strategy: AUTO }\r\n    fields:\r\n        username:\r\n            type: string\r\n            length: 255\r\n        password:\r\n            type: string\r\n            length: 255<\/pre>\n<p>Now run the <code>doctrine:generate:entities<\/code> command to create the new User entity class:<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">php app\/console doctrine:generate:entities IbwJobeetBundle<\/pre>\n<p>And update the database:<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">php app\/console doctrine:schema:update --force<\/pre>\n<p>The only requirement for your new user class is that it implements the <code>UserInterface<\/code> interface. This means that your concept of a \u201cuser\u201d can be anything, as long as it implements this interface. Open the User.php file and edit it as follows:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Entity\/User.php\">namespace IbwJobeetBundleEntity;\r\n\r\nuse SymfonyComponentSecurityCoreUserUserInterface;\r\nuse DoctrineORMMapping as ORM;\r\n\r\n\/**\r\n * User\r\n *\/\r\nclass User implements UserInterface\r\n{\r\n    \/**\r\n     * @var integer\r\n     *\/\r\n    private $id;\r\n\r\n    \/**\r\n     * @var string\r\n     *\/\r\n    private $username;\r\n\r\n    \/**\r\n     * @var string\r\n     *\/\r\n    private $password;\r\n\r\n    \/**\r\n     * Get id\r\n     *\r\n     * @return integer \r\n     *\/\r\n    public function getId()\r\n    {\r\n        return $this-&gt;id;\r\n    }\r\n\r\n    \/**\r\n     * Set username\r\n     *\r\n     * @param string $username\r\n     * @return User\r\n     *\/\r\n    public function setUsername($username)\r\n    {\r\n        $this-&gt;username = $username;\r\n\r\n    }\r\n\r\n    \/**\r\n     * Get username\r\n     *\r\n     * @return string \r\n     *\/\r\n    public function getUsername()\r\n    {\r\n        return $this-&gt;username;\r\n    }\r\n\r\n    \/**\r\n     * Set password\r\n     *\r\n     * @param string $password\r\n     * @return User\r\n     *\/\r\n    public function setPassword($password)\r\n    {\r\n        $this-&gt;password = $password;\r\n\r\n    }\r\n\r\n    \/**\r\n     * Get password\r\n     *\r\n     * @return string \r\n     *\/\r\n    public function getPassword()\r\n    {\r\n        return $this-&gt;password;\r\n    }\r\n\r\n    public function getRoles()\r\n    {\r\n        return array('ROLE_ADMIN');\r\n    }\r\n\r\n    public function getSalt()\r\n    {\r\n        return null;\r\n    }\r\n\r\n    public function eraseCredentials()\r\n    {\r\n\r\n    }\r\n\r\n    public function equals(User $user)\r\n    {\r\n        return $user-&gt;getUsername() == $this-&gt;getUsername();\r\n    }        \r\n}<\/pre>\n<p>To the generated entity we added the methods required by the <code>UserInterface<\/code> class: <code>getRoles<\/code>, <code>getSalt<\/code>, <code>eraseCredentials<\/code> and <code>equals<\/code>.<\/p>\n<p>Next, configure an entity user provider, and point it to your <code>User<\/code> class:<\/p>\n<pre class=\"lang:yaml decode:true \" title=\"app\/config\/security.yml\">...\r\n\r\n    providers:\r\n        main:\r\n            entity: { class: IbwJobeetBundleEntityUser, property: username }\r\n\r\n    encoders:\r\n        IbwJobeetBundleEntityUser: sha512<\/pre>\n<p>We also changed the encoder for our new <code>User<\/code> class to use the <code>sha512<\/code> algorithm to encrypt passwords.<\/p>\n<p>Now everything is set up but we need to create our first user. To do this we will create a new symfony command:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Command\/JobeetUsersCommand.php\">namespace IbwJobeetBundleCommand;\r\n\r\nuse SymfonyBundleFrameworkBundleCommandContainerAwareCommand;\r\nuse SymfonyComponentConsoleInputInputArgument;\r\nuse SymfonyComponentConsoleInputInputInterface;\r\nuse SymfonyComponentConsoleInputInputOption;\r\nuse SymfonyComponentConsoleOutputOutputInterface;\r\nuse IbwJobeetBundleEntityUser;\r\n\r\nclass JobeetUsersCommand extends ContainerAwareCommand\r\n{\r\n    protected function configure()\r\n    {\r\n        $this\r\n            -&gt;setName('ibw:jobeet:users')\r\n            -&gt;setDescription('Add Jobeet users')\r\n            -&gt;addArgument('username', InputArgument::REQUIRED, 'The username')\r\n            -&gt;addArgument('password', InputArgument::REQUIRED, 'The password')\r\n        ;\r\n    }\r\n\r\n    protected function execute(InputInterface $input, OutputInterface $output)\r\n    {\r\n        $username = $input-&gt;getArgument('username');\r\n        $password = $input-&gt;getArgument('password');\r\n\r\n        $em = $this-&gt;getContainer()-&gt;get('doctrine')-&gt;getManager();\r\n\r\n        $user = new User();\r\n        $user-&gt;setUsername($username);\r\n        \/\/ encode the password\r\n        $factory = $this-&gt;getContainer()-&gt;get('security.encoder_factory');\r\n        $encoder = $factory-&gt;getEncoder($user);\r\n        $encodedPassword = $encoder-&gt;encodePassword($password, $user-&gt;getSalt());\r\n        $user-&gt;setPassword($encodedPassword);\r\n        $em-&gt;persist($user);\r\n        $em-&gt;flush();\r\n\r\n        $output-&gt;writeln(sprintf('Added %s user with password %s', $username, $password));\r\n    }\r\n}<\/pre>\n<p>To add your first user run:<\/p>\n<pre class=\"toolbar:2 nums:false lang:default decode:true \">php app\/console ibw:jobeet:users admin admin<\/pre>\n<p>This will create the <code>admin<\/code> user with the password <code>admin<\/code>. You can use it to login to the admin section.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Logout\"><\/span>Logout<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Logging out is handled automatically by the firewall. All you have to do is to activate the <code>logout<\/code> config parameter:<\/p>\n<pre class=\"lang:yaml decode:true \" title=\"app\/config\/security.yml\">security:\r\n    firewalls:\r\n        # ...\r\n        secured_area:\r\n            # ...\r\n            logout:\r\n                path:   \/logout\r\n                target: \/\r\n    # ...<\/pre>\n<p>You will not need to implement a controller for the <code>\/logout<\/code> URL as the firewall takes care of everything. Let\u2019s create a route so that you can use it to generate the URL:<\/p>\n<pre class=\"lang:yaml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/config\/routing.yml\"># ...\r\n\r\nlogout:\r\n    pattern:   \/logout\r\n\r\n# ...<\/pre>\n<p>Once this is configured, sending a user to <code>\/logout<\/code> (or whatever you configure the <code>path<\/code> to be), will un-authenticate the current user. The user will then be sent to the homepage (the value defined by the <code>target<\/code> parameter).<\/p>\n<p>All left to do is to add the logout link to our admin section. To do this we will override the <code>user_block.html.twig<\/code> from SonataAdminBundle. Create the <code>user_block.html.twig<\/code> file in <code>app\/Resources\/SonataAdminBundle\/views\/Core<\/code> folder:<\/p>\n<pre class=\"lang:xhtml decode:true \" title=\"app\/Resources\/SonataAdminBundle\/views\/Core\/user_block.html.twig\">{% block user_block %}&lt;a href=\"{{ path('logout') }}\"&gt;Logout&lt;\/a&gt;{% endblock%}<\/pre>\n<p>Now, if you try to enter the admin section (clear the cache first), you will be asked for an username and password and then, the logout link will be shown in the top-right corner.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"The_User_Session\"><\/span>The User Session<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Symfony2 provides a nice session object that you can use to store information about the user between requests. By default, Symfony2 stores the attributes in a cookie by using the native PHP sessions.<\/p>\n<p>You can store and retrieve information from the session easily from the controller:<\/p>\n<pre class=\"toolbar:2 lang:php decode:true \">$session = $this-&gt;getRequest()-&gt;getSession();\r\n\r\n\/\/ store an attribute for reuse during a later user request\r\n$session-&gt;set('foo', 'bar');\r\n\r\n\/\/ in another controller for another request\r\n$foo = $session-&gt;get('foo');<\/pre>\n<p>Unfortunately, the Jobeet user stories have no requirement that includes storing something in the user session. So let\u2019s add a new requirement: to ease job browsing, the last three jobs viewed by the user should be displayed in the menu with links to come back to the job page later on.<\/p>\n<p>When a user access a job page, the displayed job object needs to be added in the user history and stored in the session:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Controller\/JobController.php\">\/\/ ...\r\n\r\npublic function showAction($id)\r\n{\r\n    $em = $this-&gt;getDoctrine()-&gt;getManager();\r\n\r\n    $entity = $em-&gt;getRepository('IbwJobeetBundle:Job')-&gt;getActiveJob($id);\r\n\r\n    if (!$entity) {\r\n        throw $this-&gt;createNotFoundException('Unable to find Job entity.');\r\n    }\r\n\r\n    $session = $this-&gt;getRequest()-&gt;getSession();\r\n\r\n    \/\/ fetch jobs already stored in the job history\r\n    $jobs = $session-&gt;get('job_history', array());\r\n\r\n    \/\/ store the job as an array so we can put it in the session and avoid entity serialize errors\r\n    $job = array('id' =&gt; $entity-&gt;getId(), 'position' =&gt;$entity-&gt;getPosition(), 'company' =&gt; $entity-&gt;getCompany(), 'companyslug' =&gt; $entity-&gt;getCompanySlug(), 'locationslug' =&gt; $entity-&gt;getLocationSlug(), 'positionslug' =&gt; $entity-&gt;getPositionSlug());\r\n\r\n    if (!in_array($job, $jobs)) {\r\n        \/\/ add the current job at the beginning of the array\r\n        array_unshift($jobs, $job);\r\n\r\n        \/\/ store the new job history back into the session\r\n        $session-&gt;set('job_history', array_slice($jobs, 0, 3));\r\n    }\r\n\r\n    $deleteForm = $this-&gt;createDeleteForm($id);\r\n\r\n    return $this-&gt;render('IbwJobeetBundle:Job:show.html.twig', array(\r\n        'entity'      =&gt; $entity,\r\n        'delete_form' =&gt; $deleteForm-&gt;createView(),\r\n    ));\r\n}<\/pre>\n<p>In the layout, add the following code before the <code>#content<\/code> div:<\/p>\n<pre class=\"lang:xhtml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/layout.html.twig\">&lt;!-- ... --&gt;\r\n\r\n&lt;div id=\"job_history\"&gt;\r\n    Recent viewed jobs:\r\n    &lt;ul&gt;\r\n        {% for job in app.session.get('job_history') %}\r\n            &lt;li&gt;\r\n                &lt;a href=\"{{ path('ibw_job_show', { 'id': job.id, 'company': job.companyslug, 'location': job.locationslug, 'position': job.positionslug }) }}\"&gt;{{ job.position }} - {{ job.company }}&lt;\/a&gt;\r\n            &lt;\/li&gt;\r\n        {% endfor %}\r\n    &lt;\/ul&gt;\r\n&lt;\/div&gt;\r\n\r\n&lt;div id=\"content\"&gt;\r\n\r\n&lt;!-- ... --&gt;<\/pre>\n<h3>Flash Messages<\/h3>\n<p>Flash messages are small messages you can store on the user\u2019s session for exactly one additional request. This is useful when processing a form: you want to redirect and have a special message shown on the next request. We already used flash messages in our project when we publish a job:<\/p>\n<pre class=\"lang:php decode:true \" title=\"src\/Ibw\/JobeetBundle\/Controller\/JobController.php\">\/\/ ...\r\n\r\npublic function publishAction($token)\r\n{\r\n    \/\/ ...\r\n\r\n    $this-&gt;get('session')-&gt;getFlashBag()-&gt;add('notice', 'Your job is now online for 30 days.');\r\n\r\n    \/\/ ...\r\n}<\/pre>\n<p>The first argument of the <code>getFlashBag()-&gt;add()<\/code> function is the identifier of the flash and the second one is the message to display. You can define whatever flashes you want, but notice and error are two of the more common ones.<\/p>\n<p>To show the flash messages to the user you have to include them in the template. We did this in the <code>layout.html.twig<\/code> template:<\/p>\n<pre class=\"lang:xhtml decode:true \" title=\"src\/Ibw\/JobeetBundle\/Resources\/views\/layout.html.twig\">&lt;!-- ... --&gt;\r\n\r\n{% for flashMessage in app.session.flashbag.get('notice') %}\r\n    &lt;div&gt;\r\n        {{ flassMessage }}\r\n    &lt;\/div&gt;\r\n{% endfor %}\r\n\r\n&lt;!-- ... --&gt;<\/pre>\n<p><a href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\" rel=\"license\"><img decoding=\"async\" style=\"border-width: 0;\" src=\"https:\/\/i.creativecommons.org\/l\/by-sa\/3.0\/88x31.png\" alt=\"Creative Commons License\" \/><\/a><br \/>\nThis work is licensed under a <a href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/3.0\/\" target=\"_blank\" rel=\"license noopener\">Creative Commons Attribution-ShareAlike 3.0 Unported License<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>* This article is part of the original Jobeet Tutorial, created by Fabien Potencier, for Symfony 1.4. Security in jobeet [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[82],"tags":[],"yst_prominent_words":[343,373,798,1369],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2259"}],"collection":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/comments?post=2259"}],"version-history":[{"count":4,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2259\/revisions"}],"predecessor-version":[{"id":133230,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/posts\/2259\/revisions\/133230"}],"wp:attachment":[{"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/media?parent=2259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/categories?post=2259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/tags?post=2259"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/intelligentbee.com\/blog\/wp-json\/wp\/v2\/yst_prominent_words?post=2259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}