2

I'm working on CakePHP 3.3. I want user to login using either email or mobile number along with password.

I have users table with email, mobile, password, etc fields.

According to CakePHP doc, I'm using custom finder auth to login.

Auth component in AppController.php

    $this->loadComponent('Auth', [
        'loginRedirect' => [
            'controller' => 'Dashboard',
            'action' => 'index'
        ],
        'logoutRedirect' => [
            'controller' => 'Pages',
            'action' => 'home'
        ],
        'authenticate' => [
            'Form' => [
                'finder' => 'auth'
            ]
        ]
    ]);

and findAuth() action in UsersTable.php

public function findAuth(Query $query, array $options)
{
    $query
        ->select(['id', 'email', 'mobile', 'password'])
        ->where(['Users.email' => $options['login']])
        ->orWhere(['Users.mobile' => $options['login']]);

    return $query;
}

and login() action in UsersController.php

public function login()
{
    if ($this->request->is('post')) {
        $user = $this->Auth->identify();
        if ($user) {
            $this->Auth->setUser($user);
            return $this->redirect($this->Auth->redirectUrl());
        }
        $this->Flash->registerError(__('Invalid Credentials, try again'), ['key' => 'registration']);
    }
}

login.ctp view contains

<?php
   echo $this->Form->create();
   echo $this->Form->input('login');
   echo $this->Form->input('password');
   echo $this->Form->submit();
   echo $this->Form->end();
?>

But this is not working and prints Invalid Credentials, try again

Update 2

Added a blank column username to users table.

login.ctp

<?php
   echo $this->Form->create();
   echo $this->Form->input('username');
   echo $this->Form->input('password');
   echo $this->Form->submit();
   echo $this->Form->end();
?>

AppController.php:authComponent

same as above

findAuth()

public function findAuth(Query $query, array $options)
{
    $query
        ->orWhere(['Users.email' => $options['username']])
        ->orWhere(['Users.mobile' => $options['username']]);

    return $query;
}

Now it's working. But why force to use username column even if not needed in application.

Gaurav
  • 131
  • 12
  • I tested with different approaches and not worked here too..maybe cakephp doesn't support for some versions and documentation is not quite enough for this. – Manohar Khadka Jan 30 '17 at 08:12

1 Answers1

1

You must SELECT all the fields you need to authenticate a user, as described on doc.

public function findAuth(Query $query, array $options)
{
    $query
        ->select(['id', 'email', 'mobile', 'username', 'password'])
        ->orWhere(['Users.email' => $options['login']])
        ->orWhere(['Users.mobile' => $options['login']]);

    return $query;
}

And be sure $options['login'] is on your form.

Update: If you are using 'login' as Form input try using:

        'authenticate' => [
            'Form' => [
                'finder' => 'auth',
                'fields' => ['username' => 'login', 'password' => 'password']
            ]
        ]

fields The fields to use to identify a user by. You can use keys username and password to specify your username and password fields respectively.

My own Query using my App (without fields => [username => login]):

SELECT 
  Users.id AS `Users__id`, 
  Users.username AS `Users__username`, 
  Users.password AS `Users__password`, 
  Users.role AS `Users__role`, 
  Users.email AS `Users__email` 
FROM 
  users Users 
WHERE 
  (
    Users.email = 'user@example.com' 
    OR (
      Users.username = 'user@example.com' 
      AND Users.username = 'user@example.com'
    )
  ) 

My login is similar, but is using username and email instead of your fields.

Update 2: The documentation is not so great. So testing I figured that by default using a custom finder the query will be modified by Cake adding the first WHERE this = 'something', then the solution is using orWhere on all the others (findAuth modified).

New Query:

SELECT 
  Users.id AS `Users__id`, 
  Users.username AS `Users__username`, 
  Users.password AS `Users__password`, 
  Users.role AS `Users__role`, 
  Users.email AS `Users__email` 
FROM 
  users Users 
WHERE 
  (
    Users.email = 'user' 
    OR Users.username = 'user'
  ) 
Alan Delval
  • 449
  • 6
  • 20
  • had tried with `select` too, but no luck... upadted question with select – Gaurav Jan 30 '17 at 09:12
  • I did exactly that in my App and it works, are you sure about ['login'] that is your Form field?... updated. – Alan Delval Jan 30 '17 at 10:57
  • how did you know using both 'finder' and 'fields' within authenticate ? that is the trick and +1 for that,this works well. – Manohar Khadka Jan 30 '17 at 11:53
  • adding `fields` has started giving `undefined index` at line `where(['Users.email' => $options['login']])`. and ` Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Users.login' in 'where clause' ` Also added `login.ctp` view in question above – Gaurav Jan 30 '17 at 11:59
  • Custom fields are not being passed down the finder, and the aliased `username` field will always be passed with the original name, ie as `username`. The only place where `login` would be found, would be in the `conditions` array. – ndm Jan 30 '17 at 12:06
  • can you explain some more @ndm . making changes according to @Alan's code is generating SQL : `SELECT Users.id AS 'Users__id', Users.email AS 'Users__email', Users.mobile AS 'Users__mobile', Users.password AS 'Users__password' FROM users Users WHERE (Users.mobile = :c0 OR (Users.login = :c1 AND Users.email = :c2)) LIMIT 1` – Gaurav Jan 30 '17 at 12:13
  • @AlanDelval It seems you have `username` column too in your table. In my case there is no `username` column. Also, in your query why `Users.username = ... AND Users.username = ...` both are same – Gaurav Jan 30 '17 at 12:39
  • Updated clarifying that. – Alan Delval Jan 30 '17 at 13:12
  • 1
    @Gaurav That is because neither of you are unsetting the conditions generated by the form authenticator, but adding to them. Before the custom finder is being called, all options are being applied on the query, in order to change that, you have to either remove/modify (`Query::traverse()` or `Query::clause()`), or overwrite (third argument of `Query::where()`) them. Also, as already mentioned, there will be no `$options['login']`, it will be `$options['username']`, no matter what alias you choose in the auth configuration. – ndm Jan 30 '17 at 13:13
  • You could change your login input to email or to mobile, and whatever user puts there, the query will check by every column, first the one with the configuration `fields [username => username]` then the ones in the finder. – Alan Delval Jan 30 '17 at 13:24
  • added `username` column to `users` table and removed `fields` from `authComponent` and replaced `login` with `username` in `login.ctp` view and also in `findAuth()`. (See update 2) Now it's working. CakePHP is forcing to use `username` column even if I don't want to use it. Anyway, Thankyou you all – Gaurav Jan 30 '17 at 15:40