Home » Php » php – Symfony authentication providers

php – Symfony authentication providers

Posted by: admin July 12, 2020 Leave a comment

Questions:

I’m using fr3d/ldap-bundle. It logs me in and imports users from AD if they’re not in db. That’s fine.

Despite AD users I also have local users, which are in my db. There is special column authType which says how user should be authenticated – via LDAP or natively ( FOS ). I’ve created my own user provider:

public function chooseProviderForUsername($username)
{
    if($user->getAuthType() == User::LOGIN_LDAP) {
         $this->properProvider = $this->ldapUserProvider;
     } elseif($user->getAuthType() == User::LOGIN_NATIVE) {
         $this->properProvider = $this->fosUserProvider;
     } else {
         throw new InvalidArgumentException('Error');
     }
}

public function loadUserByUsername($username)
{
    return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}

PROBLEM: Chain provider isn’t an option – it allows user to login with his LDAP password AND with his local password! That’s a big security issue.

Is there a way to login user via different authentication providers, depending on the db field?

EDIT:

My security.yml:

 providers:
        fos_userbundle:
            id: fos_user.user_provider.username
        appbundle_user_provider:
            id: appbundle.user_provider
        fr3d_ldapbundle:
            id: fr3d_ldap.security.user.provider

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        admin:
            pattern: ^/admin.*
            context: user
            fr3d_ldap:  ~
            form_login:
                provider: appbundle_user_provider
                csrf_provider: security.csrf.token_manager
                always_use_default_target_path: true
                default_target_path: admin_main
                login_path: /admin/login
                check_path: /admin/login_check
            logout:
                path:   /admin/logout
                target: /admin/login
            anonymous:    true

Here is security.yml. This line fr3d_ldap: ~ enables the ldap bundle, which authorize ldap users and saves them into my db. Without it I cannot authorize them, probably I would have to write custom AuthenticationProvider.

How to&Answers:

I am not very familiar with ldap but I would suggest try doing a completely manual login

  $token = new UsernamePasswordToken($user, null, "firewallname", $user->getRoles());
  $securityContext = $this->container->get('security.context');
  $securityContext->setToken($token);

Then you can manually do the checks yourself, and depending on the result of the check decide how you want to verify the user before authenticating. For example, run a query by username and password before executing this login code or whatever, depending on the db field you want.

Answer:

Your approach seems fine but you should check logic of your methods.
First of all this one:

public function chooseProviderForUsername($username)
{
    if($user->getAuthType() == User::LOGIN_LDAP) {
         $this->properProvider = $this->ldapUserProvider;
     } elseif($user->getAuthType() == User::LOGIN_NATIVE) {
         $this->properProvider = $this->fosUserProvider;
     } else {
         throw new InvalidArgumentException('Error');
     }
}

You pass $username to this method as an argument, but then use $user object, which seems to be undefined in current context.
Secondly:

public function loadUserByUsername($username)
{
    return $this->chooseProviderForUsername($username)->loadUserByUsername($username);
}

So as chooseProviderForUsername method actually does not return any value you are not able to chain it this way.

I hope refactoring these issues should make your provider work properly.

Answer:

Ok, so very brief answer, but I think at the moment Symfony is searching for the user amongst any old User Provider rather than the one you want it to for that particular user (which explains the whole logging in with two passwords thing). A solution should be to make AppBundleUserProvider implement UserProviderInterface, remove the other User Providers from security.yml and then to ensure that the first thing AppBundleUserProvider does it to find out which User Provider is required for that user then mimic it for every method in the UserProviderInterface. You could set $this->realUP based on Username, then set every method to just return $this->realUP->someMethod().

Answer:

The cleanest way I can think of is to create your own ChainProvider class that only allows login with one provider and use the Dependency Injection Container to use yours.

You just need to override the security.user.provider.chain.clas parameter definition in your bundle’s config file.