cakephp / authentication

Authentication plugin for CakePHP. Can also be used in PSR7 based applications.
MIT License
115 stars 100 forks source link

Multiple table/model fields #616

Closed joaopatrocinio closed 1 year ago

joaopatrocinio commented 1 year ago

I'm trying to implement this plugin where the database has the username decoupled from the password table, like so:

users: id username is_active

passwords: id password is_active fk_user_id

This is how the middleware is set:

        $service = new AuthenticationService();

        // Define where users should be redirected to when they are not authenticated
        $service->setConfig([
            'unauthenticatedRedirect' => Router::url('/users/login'),
            'queryParam' => 'redirect',
        ]);

        $fields = [
            IdentifierInterface::CREDENTIAL_USERNAME => 'username',
            IdentifierInterface::CREDENTIAL_PASSWORD => 'password'
        ];

        $service->loadAuthenticator('Authentication.Jwt', [
            'secretKey' => Security::getSalt(),
            'returnPayload' => false,
            'queryParam' => 'token'
            // 'header' => 'Authorization',
            // 'tokenPrefix' => 'Bearer',
        ]);

        // Configure form data check to pick username and password
        $service->loadAuthenticator('Authentication.Form', [
            'fields' => $fields,
            'loginUrl' => Router::url('/users/login'),
        ]);

        // Load identifiers, ensure we check username and password fields
        $service->loadIdentifier('Authentication.Password', [
            'fields' => $fields,
            'resolver' => [
                'className' => 'Authentication.Orm',
                'userModel' => 'Users',
                'finder' => 'auth',
            ]
        ]);

        $service->loadIdentifier('Authentication.JwtSubject', [
            'resolver' => [
                'className' => 'Authentication.Orm',
                'userModel' => 'Users',
                'finder' => 'auth',
            ]
        ]);

And this is my finder:

    public function findAuth(Query $query)
    {
        $userFields = [
            'Users.id', 
            'Users.username',
            'Passwords.password',
            'Users.is_active',
            'Passwords.is_active'
        ];
        $conditions =[
            'Users.is_active' => '1',
            'Passwords.is_active' => '1'
        ];

        $query->select($userFields)
            ->join([
                'Passwords' => [
                    'type' => 'INNER',
                    'alias' => 'Passwords',
                    'table' => 'passwords',
                    'conditions' => 'Users.id = Passwords.fk_user_id'
                ]
            ])->where($conditions);

        return $query;
    }

And I'm getting this result when I login:

APP/Controller/UsersController.php (line 26)
object(Authentication\Authenticator\Result) id:0 {
    protected _status => 'FAILURE_IDENTITY_NOT_FOUND'
    protected _data => null
    protected _errors => [
        'Password' => [
        ],
        'JwtSubject' => [
        ],
    ]
}
markstory commented 1 year ago

The result you're returning from your finder needs to have username + password accessible based on the fields configuration. See the logic used by PasswordIdentifier:

https://github.com/cakephp/authentication/blob/73cab9a3cd6938279925f3a2165d335b4ce356a5/src/Identifier/PasswordIdentifier.php#L95-L107

If you can't / don't want to make that work, then you'll need to build your own identifier implementation that works with your schema.

joaopatrocinio commented 1 year ago

Thank you, this helped me understand better how the identification process works. I was able to make the login work by creating a virtual field in the User entity. In the future I will implement an identifier which can accept array-like fields, so that I can identify with a joined table.