Ever wonder why computers sometimes ask you to prove you’re human? When a user tries to login to a website and fails twice, the computer asks the user to enter some code which is readable by human only in the third attempt, this is done to check whether the user is a machine or a human. Since hacking has touched another level, captcha is used to check whether the system is being accessed by human or a bot. This is done by using captcha at the login panel.
It isn’t a hard job to add Symfony2 Captcha to the login panel.
Adding Symfony2 Captcha To A Login Panel
FOSUserBundle is concerned with user management and user security. It is not able to differentiate between a human and a robot and to put this functionality in our “Symfony” application we have to add Symfony2 captcha in FOS Bundle’s login and registration page. Adding Symfony2 captcha in registration is easy, as we can add it simply in the “RegistrationType” file of FOSUserBundle but adding Symfony2 captcha in a login page is not as simple as on the registration page.
To go further, follow the steps below:
- Download the CodeConsortium Bundle
This is a private messaging system Bundle for Symfony projects for sending and receiving private messages. - Add the Bundle in your project by adding the following code in the Appkernel.php file.
Webmuch/Acme/App/Appkernel.php : “new CCDNUser\SecurityBundle\CCDNUserSecurityBundle(),”
- Add the following configuration in your config.yml file.
Webmuch/Acme/Config/Config.yml : ccdn_user_security: route_referer: route_ignore_list: - { bundle: 'fosuserbundle', route: 'fos_user_security_login' } - { bundle: 'fosuserbundle', route: 'fos_user_security_check' } - { bundle: 'fosuserbundle', route: 'fos_user_security_logout' } - { bundle: 'fosuserbundle', route: 'fos_user_registration_register' } - { bundle: 'fosuserbundle', route: 'fos_user_registration_check_email' } - { bundle: 'fosuserbundle', route: 'fos_user_registration_confirm' } - { bundle: 'fosuserbundle', route: 'fos_user_registration_confirmed' } - { bundle: 'fosuserbundle', route: 'fos_user_resetting_request' } - { bundle: 'fosuserbundle', route: 'fos_user_resetting_send_email' } - { bundle: 'fosuserbundle', route: 'fos_user_resetting_check_email' } - { bundle: 'fosuserbundle', route: 'fos_user_resetting_reset' } - { bundle: 'fosuserbundle', route: 'fos_user_change_password' } - { bundle: 'UserBundle', route: 'fos_user_captcha_login' } - { bundle: 'UserBundle', route: 'fos_user_captcha_login_check' } login_shield: enable_shield: true block_for_minutes: 5 limit_failed_login_attempts: before_recover_account: 3 before_return_http_500: 25 primary_login_route: name: fos_user_security_login recover_account_route: name: fos_user_captcha_login block_routes_when_denied: - fos_user_security_login - fos_user_security_check - fos_user_security_logout
Below you’ll find the Pseudocode for the code above.
The code below will cause the captcha image to be inserted on the login page if the first two attempts of login fail.
before_recover_account: 3:
This code below will cause the user to be blocked if it fails to login for 25 times in a row.
before_return_http_500: 25
The primary login route is defined for the default login page of the FOS UserBundle.
The secondary or recover route is defined for the new login page including captcha which will be build later.
primary_login_route: name: fos_user_security_login recover_account_route: name: fos_user_captcha_login
All the statements below will cause the login, logout and email check functionalities to be blocked if the user is not able to login.
block_routes_when_denied: - fos_user_security_login - fos_user_security_check - fos_user_security_logout
- After setting all the configurations properly, the forth step is to create “usercaptchalogin” which will be built inside the UserController.php file :
Webmuch/UserBundle/Controller/UserController
For this, we need to import the following statements on the top of the file “UserController.php” :
use Gregwar\Captcha\CaptchaBuilder; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; . use Gregwar\Captcha\CaptchaBuilder;
All of the code above is to build the Captcha image dynamically on the login page and to authenticate the user credentials on the basis of username and password and for the interaction of user with various events.
Now, we define the following function inside the controller for creating the “usercaptchalogin” which will build the login page including Symfony2 captcha:
public function fosUserCaptchaLoginAction(){ $builtCaptcha = new CaptchaBuilder(); $builtCaptcha->build(); $builtCaptcha->save('captcha.jpg'); return $this- >render('UserBundle:CustomizedSecurity:fosUserCaptchaLogin.html.twig',array ('captcha' => $builtCaptcha)) }
The code above is explained well in the steps below:
- The first statement will build a Symfony2 captcha image using the function CaptchaBuilder()
- The next two statements will save the build image inside the variable “builtCaptcha” in a jpg format.
- This image will then be rendered to CaptchaLogin.html.twig file which will be created later.
We define another function inside the controller which will check or validate the value of Symfony2 captcha inserted :
public function fosUserCaptchaLoginCheckAction($builtCaptcha){ $em = $this->getDoctrine()->getEntityManager(); $username = $this->getRequest()->get('_username'); $password = $this->getRequest()->get('_password'); $captcha = $this->getRequest()->get('_captcha'); $email = $this->getRequest()->get('email'); $session = $this->get('session'); $result = $em->getRepository('UserBundle:User')->findOneBy(array('email' => $username)); if(!$result){ $this->get('session')->getFlashBag()->add('error','Username is wrong'); return $this->redirect($this->generateUrl('fos_user_captcha_login')); } if (isset($result)) { $encoder_service = $this->get('security.encoder_factory'); $encoder = $encoder_service->getEncoder($result); $encoded_pass = $encoder->encodePassword($password, $result->getSalt()); if($result->getPassword() !== $encoded_pass){ $this->get('session')->getFlashBag()->add('error','Password is wrong'); return $this->redirect($this->generateUrl('fos_user_captcha_login')); }else{ if($builtCaptcha !== $captcha){ $this->get('session')->getFlashBag()->add('error','Captcha code is wrong'); return $this->redirect($this->generateUrl('fos_user_captcha_login')); }else{ $firewall = "user_secured_area"; $token = new UsernamePasswordToken($result,$password, $firewall, $result- >getRoles()); $this->get('security.context')->setToken($token); $session = $this->get('session'); $session->set('_security_'.$firewall,serialize($token)); $event = new InteractiveLoginEvent($this->getRequest(), $token); $this->get("event_dispatcher")->dispatch("security.interactive_login", $event); $router = $this->get('router'); $url = $router->generate('video'); return $this->redirect($url); } } } $this->get('session')->getFlashBag()->add('error','Your entered value is wrong'); return $this->redirect($this->generateUrl('fos_user_captcha_login')); }
The steps for the above mentioned code can be explained below:
- First we will get the values of various fields such as email, username, password and Symfony2 captcha.
- After storing these values inside the result variable we will check for the username and if the username doesn’t match, the user will be renedred to the fos_user_captcha_login’.
- If the values of username matches then it will proceed further to validate the password and the same will be done for validating Symfony2 captcha.
The entire code of the UserController.php is given as follows :
get('fos_facebook.user.login'); //todo: check if service is successfully connected. $fbService->connectExistingAccount(); return $this->redirect($this->generateUrl('sonata_user_profile_visit')); } public function loginFbAction(){ return $this->redirect($this->generateUrl("fos_user_security_login")); } public function logoutFbAction(){ return $this->redirect($this->generateUrl("fos_user_security_logout")); } private function getToken(){ return new Response($this->container->get('form.csrf_provider') ->generateCsrfToken('authenticate')); } public function fosUserCaptchaLoginAction(){ $builtCaptcha = new CaptchaBuilder(); $builtCaptcha->build(); $builtCaptcha->save('captcha.jpg'); return $this- >render('UserBundle:CustomizedSecurity:fosUserCaptchaLogin.html.twig',array('captcha' => $builtCaptcha)); } public function fosUserCaptchaLoginCheckAction($builtCaptcha){ $em = $this->getDoctrine()->getEntityManager(); $username = $this->getRequest()->get('_username'); $password = $this->getRequest()->get('_password'); $captcha = $this->getRequest()->get('_captcha'); $email = $this->getRequest()->get('email'); $session = $this->get('session'); $result = $em->getRepository('UserBundle:User')->findOneBy(array('email' => $username)); if(!$result){ $this->get('session')->getFlashBag()->add('error','Username is wrong'); return $this->redirect($this->generateUrl('fos_user_captcha_login')); } if (isset($result)) { $encoder_service = $this->get('security.encoder_factory'); $encoder = $encoder_service->getEncoder($result); $encoded_pass = $encoder->encodePassword($password, $result->getSalt()); if($result->getPassword() !== $encoded_pass){ $this->get('session')->getFlashBag()->add('error','Password is wrong'); return $this->redirect($this->generateUrl('fos_user_captcha_login')); }else{ if($builtCaptcha !== $captcha){ $this->get('session')->getFlashBag()->add('error','Captcha code is wrong'); return $this->redirect($this->generateUrl('fos_user_captcha_login')); }else{ $firewall = "user_secured_area"; $token = new UsernamePasswordToken($result,$password, $firewall, $result- >getRoles()); $this->get('security.context')->setToken($token); $session = $this->get('session'); $session->set('_security_'.$firewall,serialize($token)); $event = new InteractiveLoginEvent($this->getRequest(), $token); $this->get("event_dispatcher")->dispatch("security.interactive_login", $event); $router = $this->get('router'); $url = $router->generate('video'); return $this->redirect($url); } } } $this->get('session')->getFlashBag()->add('error','Your entered value is wrong'); return $this->redirect($this->generateUrl('fos_user_captcha_login')); } public function emailAuthenticateAction(Request $request, $token){ $em = $this->getDoctrine()->getEntityManager(); $user = $em->getRepository('UserBundle:User')->findOneBy(array('token' => $token)); if(isset($user)) { if($user->isEnabled() == false) { // Enabling The Account $user->setEnabled(true); $user->setToken(null); $em->persist($user); $em->flush(); $this->authenticateUser($user); $this->get('session')->getFlashBag()->add('success','Congratulation you are authenticated member of Candulife Style.'); return $this->redirect($this->generateUrl('home')); } } $this->get('session')->getFlashBag()->add('error','This link is invalid or has been already used'); return $this->redirect($this->generateUrl('home')); } private function authenticateUser(UserInterface $user){ $provideKey = 'user_secured_area'; $token = new UsernamePasswordToken($user,$user->getPassword(),$provideKey,$user->getRoles()); $this->get('security.context')->setToken($token); } private function generateToken(){ $key = '#}~*$/"$?&*(."*/[!%]/${"/}'; $unique = uniqid(); return $token = $unique.substr(hash('sha512',$unique.$key.microtime()), 0, 19); } }
- Fifth Step is to create “usercaptchalogin” and then we will create “fosUserCaptchaLogin.html.twig” file where the user will be rendered for login purpose inside which captcha will be introduced after login will be failed two times continously.
The code for the same is given as follows :
UserBundle/Resources/Views/Email/fosUserCaptchaLogin.html.twig : {% extends "::base1.html.twig" %} {% block userProfile %} <h2 class="gradWellHead">Login here {# count #}</h2> <div class="row-fluid marginBottom10"> <div class="span6 well"><form action="{{ path(" method="post" novalidate="" data-validate="parsley"> <div class="control-group"> <label class="control-label" for="username">Username</label> <div class="controls"><input name="_username" required="required" type="text" placeholder="Username" data-error-message="Enter your Username" data-required="true" /></div> </div> <div class="control-group"> <label class="control-label" for="password">Password</label> <div class="controls"><input name="_password" required="required" type="password" placeholder="Password" data-error-message="Enter the Password" data-required="true" /></div> </div> <div class="control-group"> <label class="control-label" for="captcha">Captcha</label> <div class="controls"><img src="{{ asset('captcha.jpg') }}" alt="" /> <input name="_captcha" required="required" type="text" placeholder="Captcha" data-error-message="Enter the Captcha code" data-required="true" /></div> </div> <div class="control-group"> <div class="controls"><label class="checkbox"> <input id="remember_me" name="_remember_me" type="checkbox" value="on" />{{ 'security.login.remember_me'|trans({}, 'FOSUserBundle') }} </label> <input id="_submit" class="btn" name="_submit" type="submit" value="{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}" /> <a href="{{ path('fos_user_resetting_request') }}">Forget Password ?</a> {{ facebook_login_button({'autologoutlink': true}) }}</div> </div> </form></div> <div class="span6 well"><img src="{{asset('img/candu_manifesto_starburst.jpg')}}" alt="" /></div> </div> {% endblock %}
I got my Symfony2 Captcha correct, hope you get yours too.
Feel free to comment for any queries or doubts. Do subscribe to our newsletter if you appreciate the tutorial.