8uld0zr
8uld0zr

Reputation: 69

How to Use multiple User Providers in symfony 5. How to chain it?

UPADATED CODE and PROBLEM :

I use symfony Symfony 5.3.6.

I have two kinds of users: company & candidate. I'd like to make them able to autenticate on their side. 2 forms are coming from front end. ( but for the moment no forms).

I use lexik_jwt_authentication.jwt_token_authenticator to authenticate my both kind of users. This is the first time I try to code for 2 providers in my security.yaml. When I had only one , it worked. When i added company, it doesnt anymore.

Here is my updated code in my security.yaml :

security:
# https://symfony.com/doc/current/security/experimental_authenticators.html
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#c-hashing-passwords
password_hashers:
    Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    App\Entity\Candidate:
        algorithm: auto
    App\Entity\Company:
        algorithm: auto


# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
    app_candidate_provider:
        entity:
            class: App\Entity\Candidate
            property: email

    app_compagny_provider:
        entity:
            class: App\Entity\Company
            property: email
    app_users:
        chain:
            providers: ['app_candidate_provider', 'app_compagny_provider']

firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    
    login:
        pattern: ^/api/login
        stateless: true
        anonymous: false
        json_login:
            check_path: /api/login
            username_path: email
            password_path: password
            success_handler: lexik_jwt_authentication.handler.authentication_success
            failure_handler: lexik_jwt_authentication.handler.authentication_failure

    api:
        pattern: ^/api/
        stateless: true
        anonymous: false
        provider: app_users
        guard:
            authenticators:
                - lexik_jwt_authentication.jwt_token_authenticator

    main:
        # anonymous: lazy
        lazy: true
        provider: app_user_provider

        # activate different ways to authenticate
        # https://symfony.com/doc/current/security.html#firewalls-authentication

        # https://symfony.com/doc/current/security/impersonating_user.html
        # switch_user: true

# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
    # - { path: ^/admin, roles: ROLE_ADMIN }
    # - { path: ^/profile, roles: ROLE_USER }

    - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/api/candidates, roles: IS_AUTHENTICATED_FULLY }
    - { path: ^/api/company, roles: IS_AUTHENTICATED_FULLY }

Now, my message error is : "Not configuring explicitly the provider for the "json_login" listener on "login" firewall is ambiguous as there is more than one registered provider.."

i have followed this thread : Not configuring explicitly the provider for the "guard" listener on "x" firewall is ambiguous as there is more than one registered provider

By remplacing

api:
            pattern: ^/api/
            stateless: true
            anonymous: false
            provider: app_users
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator

with

api:
            pattern: ^/api/
            stateless: true
            anonymous: false
            provider: 'app_candidate_provider'
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator

But still doesnt work

Do you have an idea where i make a mistake ?

EDIT : the final answer told by @mcsky is the good one :

security:
  # https://symfony.com/doc/current/security/experimental_authenticators.html
  enable_authenticator_manager: true
  # https://symfony.com/doc/current/security.html#c-hashing-passwords
  password_hashers:
      Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
      App\Entity\Candidate:
          algorithm: auto
      App\Entity\Company:
          algorithm: auto


  # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
  providers:
      app_candidate_provider:
          entity:
              class: App\Entity\Candidate
              property: email

      app_compagny_provider:
          entity:
              class: App\Entity\Company
              property: email
      app_users:
          chain:
              providers: ['app_candidate_provider', 'app_compagny_provider']

  firewalls:
      dev:
          pattern: ^/(_(profiler|wdt)|css|images|js)/
          security: false
      
      login:
          pattern: ^/api/login
          stateless: true
          provider: app_users
          anonymous: false
          json_login:
              check_path: /api/login
              username_path: email
              password_path: password
              success_handler: lexik_jwt_authentication.handler.authentication_success
              failure_handler: lexik_jwt_authentication.handler.authentication_failure

      api:
          pattern: ^/api/
          stateless: true
          anonymous: false
          provider: app_users
          guard:
              authenticators:
                  - lexik_jwt_authentication.jwt_token_authenticator

      main:
          # anonymous: lazy
          lazy: true
          provider: app_candidate_provider

          # activate different ways to authenticate
          # https://symfony.com/doc/current/security.html#firewalls-authentication

          # https://symfony.com/doc/current/security/impersonating_user.html
          # switch_user: true

  # Easy way to control access for large sections of your site
  # Note: Only the *first* access control that matches will be used
  access_control:
      # - { path: ^/admin, roles: ROLE_ADMIN }
      # - { path: ^/profile, roles: ROLE_USER }

      - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
      - { path: ^/api/candidates, roles: IS_AUTHENTICATED_FULLY }
      - { path: ^/api/company, roles: IS_AUTHENTICATED_FULLY }

Upvotes: 1

Views: 5860

Answers (1)

Mcsky
Mcsky

Reputation: 1445

You can't define one user provider with multiple classes as a configuration. It is not designed to work like this. Symfony executes this class Symfony\Bridge\Doctrine\Security\User\EntityUserProvider under the wood, as you can see it work with property and email string only.

So I suggest you define two different user providers, one per class type.

So can you try this configuration?

providers:
    app_candidate_provider:
        entity:
            class: App\Entity\Candidate
            property: email
    
    app_compagny_provider:
        entity:
            class: App\Entity\Company
            property: email
    app_users:
        chain:
            providers: ['app_candidate_provider', 'app_compagny_provider']
firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    
    login:
        pattern: ^/api/login
        stateless: true
        provider: app_users
        anonymous: false
        json_login:
            check_path: /api/login
            username_path: email
            password_path: password
            success_handler: lexik_jwt_authentication.handler.authentication_success
            failure_handler: lexik_jwt_authentication.handler.authentication_failure

    api:
        pattern: ^/api/
        stateless: true
        anonymous: false
        provider: app_users
        guard:
            authenticators:
                - lexik_jwt_authentication.jwt_token_authenticator

Let me know if something isn't clear or don't work

Upvotes: 1

Related Questions