3

I'm working in a Symfony 2.8.x app and I need to setup two secured areas: chat and admin. This means that chat and admin will use the same login template (if this is possible and I don't need to setup different one for this purpose). I have googled before ask here and there is a few things related showing up and I read a lot of post about this topic: 1, 2, 3, 4 just as an example of them but I am doing something wrong since I can't get this working properly. This is how /app/config/security.yml looks like (just the firewalls and access_control piece of code):

security:
    ....
    firewalls:
        admin:
            pattern: /admin/(.*)
            anonymous: ~
            form_login:
                provider: fos_userbundle
                csrf_provider: security.csrf.token_manager
                login_path: fos_user_security_login
                check_path: fos_user_security_check  
                use_forward: true                                           
                always_use_default_target_path: true
                default_target_path: /admin
                target_path_parameter: _target_path
                use_referer: true
                remember_me: true
            logout:
              target: /admin
            remember_me:
                secret:   '%secret%'
                lifetime: 604800 # 1 week in seconds
                path:     /
        chat:
            pattern: ^/chat/(.*)
            anonymous: ~
            form_login:
                provider: fos_userbundle
                csrf_provider: security.csrf.token_manager
                login_path: fos_user_security_login
                check_path: fos_user_security_check
                use_forward: true
                always_use_default_target_path: true
                default_target_path: /chat
                target_path_parameter: _target_path
                use_referer: true    
                remember_me: true
            logout: ~
            remember_me:
                secret:   '%secret%'
                lifetime: 604800 # 1 week in seconds
                path:     /

    access_control:
        - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

        - { path: ^/chat/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/chat/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/chat/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

        - { path: ^/chat/, role: ROLE_CHATTER }
        - { path: ^/admin/, role: ROLE_ADMIN }

Now this is the config for my bundles at app/config/routing.yml:

platform_chat:
    resource: "@PlatformChatBundle/Controller/"
    type:     annotation
    prefix:   /chat/
    options:
            expose: true

platform_admin:
    resource: "@PlatformAdminBundle/Controller/"
    type:     annotation
    prefix:   /admin/
    options:
        expose: true

And for FOSUserBundle I have tried this two (both without success and each at a time):

#FOSUser
fos_user:
    resource: "@FOSUserBundle/Resources/config/routing/all.xml"



#FOSUser    
# this second causes doubts to me since I think I will need 
# to repeat the same routes for chat prefix but I'm not sure at all
fos_user_security:
    resource: "@FOSUserBundle/Resources/config/routing/security.xml"
    prefix: /admin

fos_user_profile:
    resource: "@FOSUserBundle/Resources/config/routing/profile.xml"
    prefix: /admin/profile

fos_user_register:
    resource: "@FOSUserBundle/Resources/config/routing/registration.xml"
    prefix: /admin/register

fos_user_resetting:
    resource: "@FOSUserBundle/Resources/config/routing/resetting.xml"
    prefix: /admin/resetting

fos_user_change_password:
    resource: "@FOSUserBundle/Resources/config/routing/change_password.xml"
    prefix: /admin/profile

I have overwrite the login template at app/Resources/FOSUserBundle/views/Security/login.html.twig. (if source is needed I can provide just ommit for not make the post longer than it's already).

When I call the URL: http://domain.tld/app_dev.php/admin and try to login I got this error:

Translation not found. Context: { "id": "Symfony\Component\Security\Core\Exception\BadCredentialsException: Bad credentials. in /var/www/html/platform-cm/vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php:90\nStack trace:\n#0

(I can provide the full stacktrace if needed)

this is weird to me but perhaps it's caused for a bad configuration since I have double checked credentials.

When I call the URL: http://domain.tld/app_dev.php/chat and try to login I got Access Denied but it's right because I am redirected to http://domain.tld/app_dev.php/admin/. Can any give me some help on this configuration? I am stuck and can't move forward because of this

2nd approach

This is a second approach bassed on @heah suggestion using a listener but is not working too I am still getting the same "Bad credentials" message and can't login at all. I have changed back the routing.yml to this:

#FOSUser
fos_user:
    resource: "@FOSUserBundle/Resources/config/routing/all.xml"

I have changed back the security.yml to this:

security: ... firewalls: admin: pattern: ^/ anonymous: ~ form_login: provider: fos_userbundle csrf_provider: security.csrf.token_manager login_path: fos_user_security_login check_path: fos_user_security_check

            # if true, forward the user to the login form instead of redirecting
            use_forward: true

            # login success redirecting options (read further below)
            always_use_default_target_path: true
            default_target_path:            /admin
            target_path_parameter:          _target_path
            use_referer: true
            remember_me:    true
        logout:
          target: /admin
        remember_me:
            secret:   '%secret%'
            lifetime: 604800 # 1 week in seconds
            path:     /

access_control:
    - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/admin/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

    - { path: ^/chat/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/chat/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/chat/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

    - { path: ^/chat/, role: ROLE_CHATTER }
    - { path: ^/admin/, role: ROLE_ADMIN }

Then I have defined a listener for the event security.interactive_login as suggested in app/config/config.yml:

parameters:
    account.security_listener.class: PlatformAdminBundle\Listener\SecurityListener

Then at app/config/services.yml:

services:
    account.security_listener:
        class: %account.security_listener.class%
        arguments: ['@security.context', '@session']
        tags:
            - { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin }

And finally here is the class definition at src/PlatformAdminBundle/Listener/SecurityListener.php:

namespace PlatformAdminBundle\Listener;

use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class SecurityListener
{

    public function __construct(SecurityContextInterface $security, Session $session)
    {
        $this->security = $security;
        $this->session = $session;
    }

    public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
    {
        $user = $this->security->getToken()->getUser();
        var_export($user);
    }

}

I am running the same issue, again, perhaps I am doing something wrong and I am not seeing but I accept any ideas. What could be wrong here?

3rd approach

I have take a review to my code and change it slighty mostly following @heah suggestions. So, now security.yml is as follow:

access_control:
    - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

    - { path: ^/chat/, role: ROLE_CHATTER }
    - { path: ^/admin/, role: ROLE_ADMIN }

the changes at services.yml are basically fix the arguments since security.context has been split in Symfony 2.6+ although I am not using it at all:

services:
    ...
    account.security_listener:
        class: %account.security_listener.class%
        arguments: ['@security.authorization_checker']
        tags:
            - { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin }

And lastly the changes at the class PlatformAdminBundle/Listener/SecurityListener.php:

namespace Clanmovil\PlatformAdminBundle\Listener;

use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class SecurityListener
{

    public function __construct(AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->security = $authorizationChecker;
    }

    public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
    {
        if ($this->security->isGranted('ROLE_ADMIN')) {
            // this is something for testing
            throw new AccessDeniedException(
                'Access Denied. You are ADMIN'
            );
        } elseif ($this->security->isGranted('ROLE_CHATTER')) {
            // this is something for testing
            throw new AccessDeniedException(
                'Access Denied. You are CHATTER'
            );
        }
    }
}

When I login as user with ROLE_CHATTER everything seems to work since I got the AccessDenied exception but when I try to login as a user with ROLE_ADMIN it stop working and I come back to the initial error: Bad credentials, why is this? I am getting crazy!!

Community
  • 1
  • 1
ReynierPM
  • 17,594
  • 53
  • 193
  • 363
  • route prefixes don't need ending slash e.g `prefix : /chat` – Heah Jan 15 '16 at 12:25
  • you need to configure your access control accordingly to your new `routes.yml` config (without prefixes) – Heah Jan 15 '16 at 16:24
  • @Heah can you elaborate a answer based on your experience? I am not following you on this latest – ReynierPM Jan 15 '16 at 16:32
  • it seems you changed your `fos_user` routes in the 2nd approach (no more admin prefix) as your pattern of the firewall. So you also need to change paths in `access_control` as `/admin/login` won't match but `/login` will – Heah Jan 15 '16 at 16:47
  • @Heah ok, I have fixed but take a look to `3rd` approach, still not working under admin users – ReynierPM Jan 15 '16 at 17:39
  • I have updated my answer – Heah Jan 15 '16 at 17:46

2 Answers2

1

You need to enable translation :

# config.yml

framework:
    translator: ~
...

fos_user:
    db_driver: orm # or mongodb|couchdb|propel
    firewall_name: global
    user_class: AppBundle\Entity\User

see https://symfony.com/doc/master/bundles/FOSUserBundle/index.html

#security.yml

security:
    firewalls:
        # ...
        global:
            pattern:  ^/
            anonymous: true
            provider:  fos_userbundle
            form_login:
                csrf_token_generator: security.csrf.token_manager
                remember_me: true
                default_target_path: root
            logout:
                path: fos_user_security_logout
                target: root
            remember_me:
                secret:   '%secret%'
                lifetime: 604800 # 1 week in seconds

    access_control:
        - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }

        - { path: ^/chat/, role: ROLE_CHATTER }
        - { path: ^/admin/, role: ROLE_ADMIN }

see http://symfony.com/doc/current/components/security/authentication.html

Also you should use the same firewall since they share the same configuration and you already define access controls based on user role.

You would only need to create a core controller for '/' as :

# routing.yml
root:
    path: /
    defaults: { _controller: Your\Namespace\Controller\RootController::rootAction }

and

namespace Your\Namespace\Controller;

use Symfony\Bundle\FrameworkBundle\Controller;

class RootControler extends Controller
{
    public function rootAction()
    {
        $security = $this->get('security.authorization_checker');

        if ($security->isGranted('ROLE_ADMIN')) {
            return $this->redirectToRoute('your_admin_root');
        }

        if ($security->isGranted('ROLE_CHATTER')) {
            return $this->redirectToRoute('your_chatter_route');
        }

        return $this->redirectToRoute('fos_user_security_login');
    }
}
Heah
  • 2,349
  • 14
  • 25
  • Can you take a look to the OP? I have edited with some info but basically is not working – ReynierPM Jan 15 '16 at 14:43
  • Now I am getting this error `You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.` I am using the same setup as you – ReynierPM Jan 15 '16 at 18:19
  • work for one role and not the other ? weird ! updated my answer, please re-check your config, empty your cache and logout and close any window to force session to restart – Heah Jan 15 '16 at 23:42
  • Nothing, same issue. I have open a new question take a look [here](http://stackoverflow.com/questions/34818999/how-to-debug-and-fix-symfony23-routes) perhaps this clear a bit the issue but in this and in the other I am ending up with same issue – ReynierPM Jan 15 '16 at 23:59
  • look at my pattern for ignored firewall, no trailing `$`, have you configured the password encoder ? – Heah Jan 16 '16 at 00:12
  • Yes, it's configured. With your latest changes I end with the same error as before `You must configure the check path ....`, did you have some time? We can make a Teamviewer section if you like. – ReynierPM Jan 16 '16 at 00:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100817/discussion-between-reynierpm-and-heah). – ReynierPM Jan 16 '16 at 00:19
  • In a hour or so I'll give you the points can't right now, thanks for your help this works as should be – ReynierPM Jan 16 '16 at 03:08
0

The issue may be in the routing. Since in both cases your are using only one from FOSUserBundle to authenticate, you should try to create two different routes one for each firewall, example:

#FOSUser
fos_user_security:
    resource: "@FOSUserBundle/Resources/config/routing/security.xml"
    prefix: /admin

#FOSUser
fos_user_security:
    resource: "@FOSUserBundle/Resources/config/routing/security.xml"
    prefix: /chat
bitgandtter
  • 2,179
  • 6
  • 31
  • 60