Search code examples
symfonysymfony-3.2

Symfony 3.2 Multi tenant application with multiple Guard entry points


I am implementing a multi tenant application where the customer requires me to follow a specific data model. The customer required two seperate login systems that I am implementing using the guard system.

The first one is used to login customers. The difficulty here is that it is required to store the users passwords in the customer's database. To accomplish this I have implementation the guard system to first looks up the username in the 'login' database. From that same database the (encrypted) connection parameters for the users application database are retrieved. Using these parameters the users application database connection is created dynamically ('app') and the user is logged in using the information in this application database. On subsequent page requests the users application database is dynamically connected using a service on triggers on each pageload. So far so good.

The second Guard system is used to login to the back-end for management tasks. This login system is actually much simpler and uses a single database 'admin_login' to verify the users credentials. (So username, password and all other user properties are in one single database). The problem occurs after successfully logging in on the 2nd Guard implementation. The forward to the back-end home page triggers an error and I am unable to find to cause of this.

The error message is:

The class 'AppBundle\Entity\Login\Admin\AdminUser' was not found in the chain configured namespaces AppBundle\Entity\App

The error triggers before any code from the corresponding controller is hit. When I set the 'intercept_redirects' configuration item to true I can see that the administrative user is fully authorized before the redirect is taking place.

My doctrine configuration in config.yml

# Doctrine Configuration
doctrine:
    dbal:
        default_connection: app # specify the connexion used by default
        connections:
            login:
                driver:   pdo_mysql
                host:     '%database_login_host%'
                port:     '%database_login_port%'
                dbname:   '%database_login_name%'
                user:     '%database_login_user%'
                password: '%database_login_password%'
                charset:  utf8mb4
            admin_login:
                driver:   pdo_mysql
                host:     '%database_admin_host%'
                port:     '%database_admin_port%'
                dbname:   '%database_admin_name%'
                user:     '%database_admin_user%'
                password: '%database_admin_password%'
                charset:  utf8mb4
            app:
                driver:   pdo_mysql
                host:     '%database_app_host%'
                port:     '%database_app_port%'
                dbname:   '%database_app_name%'
                user:     '%database_app_user%'
                password: '%database_app_password%'
                charset:  utf8mb4

    orm:
        auto_generate_proxy_classes: "%kernel.debug%"

        default_entity_manager: app # specify the EM used by default (when using console commands f.e)

        entity_managers:
            login:
                connection:       login
                naming_strategy:  doctrine.orm.naming_strategy.underscore
                auto_mapping:     false
                mappings:
                    AppBundle :
                        type:     annotation
                        dir:      Entity/Login/Customer
                        prefix:   AppBundle\Entity\Login\Customer
                        alias:    Login
            app:
                connection:       app
                naming_strategy:  doctrine.orm.naming_strategy.underscore
                auto_mapping:     false
                mappings:
                    AppBundle :
                        type:     annotation
                        dir:      Entity/App
                        prefix:   AppBundle\Entity\App
                        alias:    App
                filters:
                    customer_flt:
                        class:    AppBundle\Security\CustomerFilter
            admin:
                connection:       admin_login
                naming_strategy:  doctrine.orm.naming_strategy.underscore
                auto_mapping:     false
                mappings:
                    AppBundle :
                        type:     annotation
                        dir:      Entity/Login/Admin
                        prefix:   AppBundle\Entity\Login\Admin
                        alias:    Admin

My security.yml

security:

    encoders:
        AppBundle\Entity\App\User:
          algorithm: bcrypt
          cost: 12
        AppBundle\Entity\Login\Admin\AdminUser:
          algorithm: bcrypt
          cost: 12

    role_hierarchy:
        ROLE_SUPER_ADMIN: ROLE_ADMIN
        ROLE_ADMIN:       ROLE_USER
    # http://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
    providers:
        app:
            entity:
                class: AppBundle\Entity\App\User
                property: username

        admin:
            entity:
                class: AppBundle\Entity\Login\Admin\AdminUser
                property: login

    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            pattern:  ^/app
            anonymous: ~
            guard:
                authenticators:
                    - app.security.login_form_authenticator
            logout:
               path:   /app/logout
               target: /
            provider: app

        admin:
            pattern:  ^/admin
            anonymous: ~
            guard:
                authenticators:
                    - backend.security.login_form_authenticator
            logout:
               path:   /admin/logout
               target: /
            provider: admin

    access_control:
            - { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/admin, roles: ROLE_SUPER_ADMIN }
            - { path: ^/app/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
            - { path: ^/app, roles: ROLE_USER }

What am I missing here?


Solution

  • Issue solved! Just an update so that everyone can find the answer when googling for it.

    The issue was caused by a missing manager_name property in the providers section of the security.yml file. This property provides a mapping from a provider to a specific entity_manager. Without these Symfony takes the first (or default) entity manager and that fails (obviously) in my scenario.

    providers:
        app:
            entity:
                class: AppBundle\Entity\App\User
                property: username
                manager_name: app
    
        admin:
            entity:
                class: AppBundle\Entity\Login\Admin\AdminUser
                property: login
                manager_name: admin
    

    Sparse documentation of this can be found here: http://symfony.com/doc/current/security/entity_provider.html