4

Is it possible to perform an automatic redirect to the some route (i.e. /) for the specific route /login only for users that are AUTHENTICATED? and How?

I'm using FOSUserBundle.

This is my security configuration:

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: sha512

role_hierarchy:
    ROLE_ADMIN:       ROLE_USER
    ROLE_SUPER_ADMIN: ROLE_ADMIN

providers:
    fos_userbundle:
        id: fos_user.user_provider.username_email

firewalls:
    main:
        pattern: ^/
        form_login:
            provider: fos_userbundle
            csrf_provider: form.csrf_provider
            login_path: /accedi
            check_path: /login_check
            default_target_path: /
        oauth:
            resource_owners:
                facebook:           "/login/check-facebook"
                google:             "/login/check-google"
            login_path:        /accedi
            failure_path:      /accedi
            default_target_path: /

            oauth_user_provider:
                service: my_user_provider
        logout:
            path: /logout
            target: /
            invalidate_session: false
        anonymous:  ~
    login:
        pattern:  ^/login$
        security: false

        remember_me:
            key: "%secret%"
            lifetime: 31536000 # 365 days in seconds
            path: /
            domain: ~ 

    oauth_authorize:
        pattern:    ^/oauth/v2/auth
        form_login:
            provider: fos_userbundle
            check_path: _security_check
            login_path: _demo_login
        anonymous: true
    
    oauth_token:
        pattern:    ^/oauth/v2/token
        security:   false
        
        
access_control:
    - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/accedi$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/registrati, role: IS_AUTHENTICATED_ANONYMOUSLY }
Community
  • 1
  • 1
Angelo Giuffredi
  • 923
  • 3
  • 13
  • 26
  • 1
    What do you mean by `hide` the route? An automatic redirect to i.e. `/`? – Nicolai Fröhlich Nov 25 '13 at 10:53
  • @nifr exactly, an automatic redirect onlu for authenticated users – Angelo Giuffredi Nov 25 '13 at 11:05
  • 1
    Could you add your firewall-configuration to the question? are you using a `route_name` or a `/path` for the `security.firewalls.your_firewall.form_login.login_path` config? Are you using your own controller method to render the login form or are you using an open-source bundle i.e. FOSUserBundle? – Nicolai Fröhlich Nov 25 '13 at 11:09

5 Answers5

11

As you are using FOSUserBundle the rendering of the login form takes place in SecurityController::renderLogin().

The solution is bascially:

  • overriding the SecurityController
  • adding a check for the role IS_AUTHENTICATD_ANONYMOUSLY
  • redirecting the user to another page if the role was not found

I assume you have already created a bundle extending FOSUserBundle which holds your User Entity.

I assume this bundle is called YourUserBundle and is located at src/Your/Bundle/UserBundle.

Now copy (not cut) the SecurityController

vendor/friendsofsymfony/user-bundle/src/FOS/UserBundle/Controller/SecurityController.php

to (in order to override the one provided by FOSUserBundle)

src/Your/Bundle/UserBundle/Controller/SecurityController.php

add the use-statement for RedirectResponse and edit the renderLogin() method like this:

use Symfony\Component\HttpFoundation\RedirectResponse;

// ...

protected function renderLogin(array $data)
{
    if (false === $this->container->get('security.context')->isGranted('IS_AUTHENTICATED_ANONYMOUSLY')) {
        return new RedirectResponse('/', 403);
    }

    $template = sprintf('FOSUserBundle:Security:login.html.%s', $this->container->getParameter('fos_user.template.engine'));

    return $this->container->get('templating')->renderResponse($template, $data);
}

Update

Now instead of security.context use security.authorization_checker.

http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements

yuriy636
  • 11,171
  • 5
  • 37
  • 42
Nicolai Fröhlich
  • 51,330
  • 11
  • 126
  • 130
  • Which improvement do you mean? I wanted to keep the example as simple as possible but if the code is not working in it's current form (i didn't test it) please edit my answer or leave a comment. – Nicolai Fröhlich Nov 25 '13 at 14:39
  • I saw you wanted to add getting the router from the container to generate the route to redirect to in your suggested edit. actually the question asks how to redirect to `/` so let's keep it simple :) – Nicolai Fröhlich Nov 25 '13 at 14:42
  • Thanks @nifr . For some reason the `if(false=== ...)` does not work for me. Any idea what could be wrong with my setup? My authenticated users always have role 'ROLE_USER' so I am checking for that role instead. Here is my working code for sf 2.3 `if (true === $this->container->get('security.context')->isGranted('ROLE_USER')) { return new RedirectResponse($this->container->get('router')->generate('myroutename')); }` – mirk Jan 08 '14 at 22:53
  • 1
    @mirk: I use `$this->container->get('security.context')->getToken()->isAuthenticated()` for the check. – ownking Mar 25 '14 at 16:19
  • Please answer here https://stackoverflow.com/questions/46484181/symfony-redirect-if-already-loggedin-from-login-page – Aamir Sep 29 '17 at 07:53
7

It seems to me overriding the rendering of the login form is providing an answer in the wrong place. The rendering of the login form is not the one responsible for login. It's a result of the login request. It could have other usages elsewhere in the future and you'd be breaking functionality for those situations.

Overriding the login action seems better to me. That's the actual component responsible for handling the login request.

To do that, override the login action in the Security Controller. Say you have a MyUserBundle in your MyProject project which extends the FOSUserBundle.

<?php

namespace MyProject\MyUserBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use FOS\UserBundle\Controller\SecurityController as BaseSecurityController;

class SecurityController extends BaseSecurityController
{

    /**
     * Overriding login to add custom logic.
     */
    public function loginAction(Request $request)
    {
        if( $this->container->get('security.context')->isGranted('IS_AUTHENTICATED_REMEMBERED') ){

            // IS_AUTHENTICATED_FULLY also implies IS_AUTHENTICATED_REMEMBERED, but IS_AUTHENTICATED_ANONYMOUSLY doesn't

            return new RedirectResponse($this->container->get('router')->generate('some_route_name_in_my_project', array())); 
            // of course you don't have to use the router to generate a route if you want to hard code a route
        }

        return parent::loginAction($request);
    }
}
Prynz
  • 551
  • 6
  • 10
  • 1
    I can't get this to work, I copied the code above but I get "ContextErrorException: Runtime Notice: Declaration of xxxx shoud be compatible fos security controller loginAction" – Daniel Harper Apr 13 '14 at 20:26
  • This usually happens when there is a mismatch of your definition of an overridden method with that of the parent class. It could be that you are using a different version of the bundle where the method signature is different from the version used above. Check the method signature in the parent class to see if it requires the same parameters as you used in your subclass. If 'xxxx' is loginAction, I'm guessing you missed a parameter for the loginAction(Symfony\Component\HttpFoundation\Request) method or that your Request parameter is not of the Symfony\Component\HttpFoundation\Request class. – Prynz May 22 '14 at 07:56
  • 2
    just use $this->container->get('security.authorization_checker') instead (new in 2.6) – Ronan Aug 30 '15 at 20:29
2

NB : I'm using Symfony 3.0.4@dev

This answer is based and the one that @nifr @mirk have provided and the comment of @Ronan.

To prevent the user access to the login page I override the SecurityController like this :

<?php

namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\RedirectResponse;
use FOS\UserBundle\Controller\SecurityController as BaseController;

class SecurityController extends BaseController
{

    /**
     * Renders the login template with the given parameters. Overwrite this function in
     * an extended controller to provide additional data for the login template.
     *
     * @param array $data
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function renderLogin (array $data)
    {
        // This little if do the trick
        if ($this->container->get('security.authorization_checker')->isGranted('ROLE_USER')) {
            return new RedirectResponse($this->container->get ('router')->generate ('app_generation_page'));
        }

        $template = sprintf ('FOSUserBundle:Security:login.html.twig');
        return $this->container->get ('templating')->renderResponse ($template, $data);
    }
}

I've also added it to the RegistrationController to do the exact same thing.

Hope it will help some of you.

Fikkwix
  • 167
  • 3
  • 10
1

Another solution based on the @nifr answer. Overwriting only the renderLogin function in your bundle controller. See also. How to use Bundle Inheritance to Override parts of a Bundle

namespace MyProject\UserBundle\Controller;
//namespace FOS\UserBundle\Controller;

 use Symfony\Component\HttpFoundation\RedirectResponse;
 use \FOS\UserBundle\Controller\SecurityController as BaseController;

 class SecurityController extends BaseController {

/**
 * Renders the login template with the given parameters. Overwrite this function in
 * an extended controller to provide additional data for the login template.
 *
 * @param array $data
 *
 * @return \Symfony\Component\HttpFoundation\Response
 */
  protected function renderLogin(array $data) {

    if (true === $this->container->get('security.context')->isGranted('ROLE_USER')) {
        return new RedirectResponse($this->container->get('router')-  >generate('homeroute'));
    }

    $template = sprintf('FOSUserBundle:Security:login.html.%s', $this->container->getParameter('fos_user.template.engine'));

    return $this->container->get('templating')->renderResponse($template, $data);
  }

}  
mirk
  • 442
  • 4
  • 13
  • just use $this->container->get('security.authorization_checker') instead (new in 2.6) – Ronan Aug 30 '15 at 20:29
  • I tried this solution with Symfony 3.2 and didn't work. Authorization was ok but redirection based on role didn't work. If clause were always false . I accomplished this by doing what this post says http://stackoverflow.com/a/31167310/1830720 – Rodolfo Velasco Apr 24 '17 at 14:45
-1

I'm using the routing and security to enable this.

#routing.yml
index:
    path: /
    defaults: { _controller: AppBundle:Base:index }
    methods: [GET]

login:
    path: /login
    defaults: { _controller: AppBundle:Security:login }
    methods: [GET]

If a user is logged in, he get redirected to the dashboard. If not, he will see the login route.

#security.yml
    access_control:
        - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/dashboard, role: ROLE_USER }

Hope this helps you. :)

kristof
  • 129
  • 1
  • 14