0
votes

Heyo!

I know it's a common problem people having problems with custom providers and web service authentication. I'm spending hours trying to figure out how to do that but I'm almost freaking out.

So, the thing is: I'm using the Symfony Firewalls with a custom UserProvider and a AbstractGuardAuthenticator as well. The problem is in the loadUserByUsername($username) function inside the UserProvider implementation.

For security reasons I can't retrieve the user password from Parse (my web service), and the loadUserByUsername($username) function asks for that. Even in the documentation about how to create a custom user provider using web services they are retrieving the user password from the database.

So what's the solution in that case? What can I do when I don't have access to the user password?

My current code is something like that:

$app['app.authenticator'] = function () {
    return new Authenticator($app);
};


$app['security.firewalls'] = array(
    'login' => array(
        'pattern' => '^/login/$',
    ),
    'secured' => array(
        'pattern' => '^.*$',
        'form' => array('login_path' => '/login/', 'check_path' => '/login/auth/'),
        'logout' => array('logout_path' => '/logout/', 'invalidate_session' => true),
        'guard'          => array(
            'authenticators'  => array(
                'app.authenticator'
            ),
        ),
        'users' => function () use ($app) {
            return new UserProvider($app);
        },
    )
);

The Authenticator.php is quite big code because extends the AbstractGuardAuthenticator class. But I'm basically using this one from Symfony docs. The only thing Is that I'm sending to the UserProvider class the username AND the password as well, because that way I can check if the user and password are right. Like this:

public function getUser($credentials, UserProviderInterface $userProvider) {
    return $userProvider->loadUserByUsername($credentials);
}

And my UserProvider class is the default one, I'm just checking inside the loadUserByUsername function if the credentials comming from my Authenticator are right. Something like this:

public function loadUserByUsername($credentials) {
    $encoder = new BCryptPasswordEncoder(13);

    try {
        $user = ParseUser::logIn($credentials['username'], $credentials['password']);
    } catch (ParseException $error) {
        throw new UsernameNotFoundException(sprintf('Invalid Credentials.'));
    }

    return new User($credentials['username'], $encoder->encodePassword($credentials['password'], ''), explode(',', 'ROLE_USER'), true, true, true, true);
}

The problem is: after the login (everything with the login is working fine), Silex calls the loadUserByUsername function in every page which needs to be secured, but just sending the username parameter.

So basically, I don't know what to do guys. I'm really trying to figure out how to get this thing working.

Thanks for your help!

1
It would be helpful to see your whole implementation. Have you a custom authentificator and a user model? If you know that the password is right, you could just use this password (why try to get the same password from ParseUser?) and username to create a new user model and to return this. - Rawburner
Hey @Rawburner, thanks for your time! I just updated my question with some code. Let me know it's more clear now. I'm using the default User model. So, there's a way to save the password in the User model and use it anytime I want? Tell me more about that :-) - Moisés Pio

1 Answers

0
votes

I have a similar implementation and this issue is well known. In my user provider I have the methods loadUserByUsername($username) and refreshUser(UserInterface $user). Since I have the same issue like you, I don't check the user in loadUserByUsername but simple return a new Object with only the username in it to not disturb the flow. loadUserByUsername doesn't make sense for external APIs, so I simply jump over it. The method refreshUser is either beeing called on every request, this is usefull.

In your AbstractGuardAuthenticator you have the method createAuthenticatedToken, which returns an token. There you should have the full authentificated user:

abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface
{
    /**
     * Shortcut to create a PostAuthenticationGuardToken for you, if you don't really
     * care about which authenticated token you're using.
     *
     * @param UserInterface $user
     * @param string        $providerKey
     *
     * @return PostAuthenticationGuardToken
     */
    public function createAuthenticatedToken(UserInterface $user, $providerKey)
    {

        //do login stuff
        //save password in user

        return new PostAuthenticationGuardToken(
            $user,
            $providerKey,
            $user->getRoles()
        );
    }
}

Then, I would't use loadUserByUsername but refreshUser instead. Both are called on every request:

/**
 * Don't use
 * @codeCoverageIgnore
 * @param string $username
 * @return User
 */
public function loadUserByUsername($username)
{
    return new User($username,  null, '', ['ROLE_USER'], '');
}

/**
 * Refresh user on every subrequest after login
 * @param UserInterface $user
 * @return User
 */
public function refreshUser(UserInterface $user)
{

    $password = $user->getPassword();
    $username = $user->getUsername();
    //login check
}