CakePHP Auth with two tables (models) - cakephp

I have two tables in my database, one for admins (named Admins) and the other one for normal users, named : utilisateurs (in french). I know i have to use cakePHP's convention which says i must create a table named users, which has the fields username and password. But the problem is that i have two tables and not only one. Each one has its particular fields, so i really need them to stay separated. And even informations required to login are different depending on the user :
admins need their login + password
normal users need a specific id (in their id cards) + password
What i want to do, is create two login pages, one for admins and the other for normal user. After logging in, the user is redirected to the page he is supposed to see. But yet, if the user tries attempt a forbidden location. I want be able to stop him (beforeFilter + isAuthorized i think)
How can i make all this work ?
I'm not beginner in cakephp, i've already made an authentication system in anotehr app using Auth Component, but it was a little easier because needed only one table for users.
Your help would be much appreciated.

Assuming the following:
You have 2 tables associated with model User and Admin, where:
User has idcard and password fields.
Admin has login and passowrd field.
The passwords are hashed using User::hasPassword and Admin::hasPassword functions.
You login form is created as follows:
echo $this->Form->create(null, '') ;
echo $this->Form->input('login') ;
echo $this->Form->input('password') ;
echo $this->Form->end(__('Submit')) ;
You can create a new Authenticate component under App/Controller/Component/Auth/MyAuthenticate.php:
<?php
App::uses('FormAuthenticate', 'Controller/Component/Auth');
class MyAuthenticate extends FormAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
$username = $request->data['login'] ;
$password = $request->data['password'] ;
App::import('Model', 'User') ;
$userModel = new User () ;
/* Try to authenticate as a user... */
$user = $userModel->find('first', array(
'conditions' => array(
'idcard' => $username,
'password' => User::hashPassword($password) ;
)
)) ;
if ($user) {
$user = $user['User'] ; // Get only useful info
$user['type'] = 'user'; // Save user type
return $user ;
}
/* Same thing for admin. */
App::import('Model', 'Admin') ;
$adminModel = new Admin () ;
$user = $adminModel->find('first', array(
'conditions' => array(
'login' => $username,
'password' => Admin::hashPassword($password) ;
)
)) ;
if ($user) {
$user = $user['Admin'] ; // Get only useful info
$user['type'] = 'admin'; // Save user type
return $user ;
}
return null ;
}
};
You just need to be sure that that a admin cannot be authenticated as a user, and reverse.
In your AppController:
public $components = array(
'Auth' => array(
'authenticate' => array('My'), // The prefix in front of your component
'loginAction' => array(/* ... */),
'loginRedirect' => array(/* ... */),
'logoutRedirect' => array(/* ... */),
'authError' => "...",
'authorize' => 'Controller'
)
) ;
Your login action is the same that for normal tables:
public function login () {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
}
else {
$this->request->data['password'] = "" ;
$this->Session->setFlash('Invalid login/id or password.');
}
}
}
Then, in beforeFilter or isAuthorized you can check $this->Auth->user('type');. For example, in AppController:
public function isAuthorized () {
/* Assuming you keep CakePHP convention where action starting with admin_ set admin params. */
if(isset($this->params['admin'])) {
return $this->Auth->user('type') == 'admin' ;
}
return true ;
}
Or if you want to disable access for non admin users to all action in AdminController, use beforeFilter:
class AdminController extends AppController {
public function beforeFilter () {
if (!$this->Auth->loggedIn()) {
$this->Session->setFlash('You need to be logged to access this page.');
$this->redirect(/* Login url. */) ;
return ;
}
if ($this->Auth->user('type') != 'admin') {
$this->Session->setFlash('You need to be admin to access this page.');
$this->redirect(/* Somewhere... */) ;
return ;
}
return parent::beforeFilter () ;
}
}

Related

how to make login with only one field + cakephp 2.x

I am trying to make a login by getting/authorizing only one input *user_number* (Not username - password).
I made my current login page with the following way:
Cakephp2.x simple login
Any help plz!
Keep it simple
If you only have one way of identifying users, the simplest (and therefore recommended) way to identify users would be to define your own login function. e.g.:
public function login() {
if ($this->request->is('post')) {
$number = $this->request->data['User']['user_number'];
$user = $this->User->findByUserNumber($number);
if ($user && $this->Auth->login($user)) {
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Session->setFlash(__('User %d doesn\'t exist', $number), 'default', array(), 'auth');
}
}
}
Note that this varies very little from the standard way of logging a user in with Cake 2.x
Create a Custom Authentication object
Create a Custom Authentication object that authenticates uses by user-number only;
Creating Custom Authentication objects
app/Controller/Component/Auth/UserNumberAuthenticate.php
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
class UserNumberAuthenticate extends BaseAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
$userModel = $this->settings['userModel'];
list($plugin, $model) = pluginSplit($userModel);
$fields = $this->settings['fields'];
if (
empty($request->data[$model])
|| empty($request->data[$model][$fields['username']])
) {
return false;
}
return $this->_findUser($request->data[$model][$fields['username']]);
}
/**
* Find a user record via his user-number/identifier
*
* #param string $usernumber The user-number/identifier.
* #return Mixed Either false on failure, or an array of user data.
*/
protected function _findUser($usernumber) {
$userModel = $this->settings['userModel'];
list($plugin, $model) = pluginSplit($userModel);
$fields = $this->settings['fields'];
$conditions = array(
$model . '.' . $fields['username'] => $usernumber,
);
if (!empty($this->settings['scope'])) {
$conditions = array_merge($conditions, $this->settings['scope']);
}
$result = ClassRegistry::init($userModel)->find('first', array(
'conditions' => $conditions,
'recursive' => $this->settings['recursive'],
'contain' => $this->settings['contain'],
));
if (empty($result) || empty($result[$model])) {
return false;
}
$user = $result[$model];
unset($result[$model]);
return array_merge($user, $result);
}
}
Then specify that you want to use your custom authentication object
Inside your AppController:
public $components = array(
'Auth' => array(
'authenticate' => array(
'UserNumber' => array(
'userModel' => 'User',
'fields' => array('username' => 'user_number')
)
)
)
);

LDAP Authentication with CakePHP

I'm trying to create an app where the authorization part is done by checking the input criteria against LDAP, on CakePHP. I'm following the text linked in this page but I'm having issues. I don't want to use any users table, I have no interest in storing the data. All I want to do, is grant the access to the users in case they have any LDAP credentials. I managed to estabilish the connection and check the data, but the app won't save the session, so the user is never logged in. Do you have any clue on what am I doing wrong?
This is the login function:
public function login() {
/*if($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash("Invalid data provided");
}
}*/
App::import('Lib', 'ldap');
if ($this->Session->read('Auth.User')) {
$this->redirect(array('controller' => 'allocations', 'action' => 'index'));
} elseif (!empty($this->data)) {
$ldap = new ldap;
if ($ldap->auth($this->Auth->request->data['User']['username'], $this->Auth->request->data['User']['password'])) {
/*$userrow = $this->User->findByUsername($this->data['User']['username']);
if (!$userrow) {
$ldap_info = $ldap->getInfo($this->data['User']['username']);
$this->data['User']['user'] = $this->data['User']['username'];
$this->data['User']['name'] = $ldap_info['name'];
$this->data['User']['group_id'] = 3; //sets the default group
$this->add();
$userrow = $this->User->findByUsername($this->data['User']['user']);
}
$user = $userrow['User'];*/
$user = array()
$this->Auth->Session->write($this->Auth->sessionKey, $user);
$this->Auth->_loggedIn = true;
$this->redirect($this->Auth->redirect());
$this->Session->setFlash('You are logged in!');
} else {
$this->Session->setFlash(__('Login Failed', true));
}
}
}
I tried debugging the ldap->auth (which basically calls an ldap_bind function stored in an external library) and the result of the auth is correct (the method returns 'true'). So the problem has to be in the creation of the session. How do I make CakePHP know that the user has been correctly logged in and that it has to store the session? Thanks!
you can write user data from ldap into session by
$this->Session->write('Auth.User', array(
'id' => 1,
'username' => 'admin'
));
I use code above to set user in session. it's work with AuthComponent.
you can check/get user detail by AuthComponent::user()

cakephp login redirect

I have a user front end and an admin area. If a user is signed in and trys to go to the to the admin url they are redirected to the index page. I wish to redirect them to the admin login page with a message to login as administrator.
There may be a case where a admin is logged in as a user and then trys to login into the admin area. I have not been able to rediect to the admin login and give option to log out and log in as admin.
app_controller
function beforeFilter() {
$this->Auth->loginError = "Wrong credentials";
$this->Auth->authError = "This part of the website is protected.";
//Configure AuthComponent
$this->Auth->allow('display');
$this->Auth->authorize = 'actions';
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
//$this->Auth->autoRedirect = false;
//$this->Auth->loginRedirect = array('controller' => 'reservatins', 'action' => 'index');
} // end before filter
users_controller
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allowedActions = array('admin_login','admin_logout');
//$this->Auth->allowedActions = array('*');
$this->set('select_nav', array('admin','users'));
}
function admin_login() {
// $this->layout = 'admin'; // nothing required
$this->layout = 'blank'; // nothing required
}
I have done that on one of my projects. The user is ever logged in (as Anonymous, as User or as Admin) and, depending on from where is he coming, and the current permissions he have, I show different login errors.
To do that.. this is what I did...
First, you need to use the "controller" authorize method:
$this->Auth->authorize = 'controller';
From now on, all your actions will pass through the isAuthorized method of your current controller. As I have my users, groups and permissions on my database and every group have different permissions, I created the isAuthorized method on my app_controller:
public function isAuthorized()
{
if ( !$this->__permitted($this->name, $this->action) )
{
$this->cakeError('error403');
return false;
}
return true;
}
What I'm doing here is checking for user permissions through my AppController __permitted method (it simply checks for permissions on session; if we don't have them saved in session, I check for them on the DB and then I store them on the Session).
If the user don't have permissions, I show him the error 403. And here is the funny part.
In your AppError add a method called error403, and here you can control where to redirect the user and what kind of message to show to him.
Here is the code I've used (obviously you must create your own piece of code according to your needs):
public function error403()
{
// Extract params
extract($this->controller->params, EXTR_OVERWRITE);
// Store url to be redirected on success
if (!isset($url))
{
$url = $this->controller->here;
}
if (isset($url['url']))
{
$url = $url['url'];
}
$url = Router::normalize($url);
// The page is trying to access is an admin page?
$is_admin_page = isset($this->controller->params['admin']) && $this->controller->params['admin'] == true ? true : false;
if (!empty($url) && count($url) >= 2)
{
$query = $url;
unset($query['url'], $query['ext']);
$url .= Router::queryString($query, array());
}
// 403 header
$this->controller->header("HTTP/1.0 403 Forbidden");
// If my method is NOT an upload
if (!preg_match('/upload/', $url))
{
// Write referer to session, so we can use it later
$this->controller->Session->write('Auth.redirect', $url);
}
else exit; // else exit, so we prevent 302 header from redirect
// NOTE: we can't use $this->controller->Auth->loginAction because there's no controller loaded
$loginAction = array('controller' => 'users', 'action' => 'login');
// If is ajax...
if (isset($this->controller->params['isAjax']) && $this->controller->params['isAjax'] == true)
{
$this->controller->layout = 'ajax';
$message = __("No tens permisos per fer aquesta acció", true);
// If user is anonymous..
if ( $this->controller->ISession->isAnonymous() )
{
// AJAX Error Message
$message = __('La teva sessió no està iniciada.', true)
. ' <a href="'.Router::url($loginAction).'">'
. __('Fes clic aquí per iniciar-la', true) . '</a>';
}
$this->controller->set(compact('message'));
$this->controller->render('error403');
$this->controller->afterFilter();
echo $this->controller->output;
}
else
{
$message = __("No tens permisos per fer aquesta acció", true);
$redirect = $this->controller->referer();
// If is anonymous...
if ($this->controller->ISession->isAnonymous())
{
$message = __('La teva sessió no està iniciada.', true);
$redirect = $loginAction;
}
// If user can't access the requested page, we redirect him to login
if (!$this->controller->ISession->userCan($redirect))
{
$redirect = $loginAction;
}
// Show different auth messages for admin and user pages
$this->controller->Session->setFlash($message, $is_admin_page ? 'default' : 'gritter', array(), 'auth');
$this->controller->redirect($redirect, null, true);
}
}
Remember, this is the code for my case. You should create your own error403 page according to your needs. Of course, you can start with my method to get it :)

Using username instead of email in CakePHP's Auth Component

Using CakePHP's Auth Component, how do I allow users to authenticate by using either their "username" or "email" field as a username, and a "pass" field as their password?
what does "using (username and email) both as username " mean?
Edit: ok, so you want Auth to look in both username and email fields in the db to compare to the "username" that the user enters? then do this:
function beforeFilter() {
parent::beforeFilter();
$this->Auth->fields = array('username' => 'username', 'password' => 'pass');
$this->Auth->autoRedirect = false;
}
function login(){
if ($this->Auth->user()) {
$this->redirect($this->Auth->redirect());
} else if (!empty($this->data)) {
$this->Auth->fields = array('username' => 'email', 'password' => 'pass');
$this->data['User']['email'] = $this->data['User']['username'];
if($this->Auth->login($this->data))$this->redirect($this->Auth->redirect());
}
}
To do this you have to skip Auths autoredirect and manage it yourself. This the login action in your users_controller:
public function login() {
if(!empty($this->data)) { // Submitted form
// Try to login with Email
if(!$this->Auth->user() // if user wasn't logged in with username + pass
&& !empty($this->Auth->data['User']['username'])
&& !empty($this->Auth->data['User']['password'])
) {
$user = $this->User->find('first', array(
'conditions' => array(
'User.email' => $this->Auth->data['User']['username'],
'User.password' => $this->Auth->data['User']['password']
),
'recursive' => -1
));
if(!empty($user) && $this->Auth->login($user)) {
// They logged in, so kill the flash error message
$this->Session->delete('Message.auth');
} else {
$this->Session->setFlash($this->Auth->loginError, $this->Auth->flashElement, array(), 'auth');
}
}
if($this->Auth->user()) {
// Post login logic here
$this->redirect($this->Auth->redirect());
}
} else {
if($this->Auth->user()) {
$this->Session->setFlash(__d('users', 'You are already registered and logged in!', true));
//$this->redirect('/');
$this->redirect($this->Auth->redirect());
}
}
This was copied straight from my app, so may need a bit of tweaking for yours. Don't forget to set $this->Auth->autoRedirect = false; in your AppController:beforeFilter();
You have to remember that Auth will automatically check against username and password, so this action just picks up from that. The Session::remove() call is to delete the Auth error message automatically left when the username/password check fails ANd the email login succeeds (otherwise you get error messages with successful logins).

Is there a better way to change user password in cakephp using Auth?

I am learning cakephp by myself. I tried to create a user controller with a changepassword function. It works, but I am not sure if this is the best way, and I could not googled up useful tutorials on this.
Here is my code:
class UsersController extends AppController {
var $name = 'Users';
function login() {
}
function logout() {
$this->redirect($this->Auth->logout());
}
function changepassword() {
$session=$this->Session->read();
$id=$session['Auth']['User']['id'];
$user=$this->User->find('first',array('conditions' => array('id' => $id)));
$this->set('user',$user);
if (!empty($this->data)) {
if ($this->Auth->password($this->data['User']['password'])==$user['User']['password']) {
if ($this->data['User']['passwordn']==$this->data['User']['password2']) {
// Passwords match, continue processing
$data=$this->data;
$this->data=$user;
$this->data['User']['password']=$this->Auth->password($data['User']['passwordn']);
$this->User->id=$id;
$this->User->save($this->data);
$this->Session->setFlash('Password changed.');
$this->redirect(array('controller'=>'Toners','action' => 'index'));
} else {
$this->Session->setFlash('New passwords differ.');
}
} else {
$this->Session->setFlash('Typed passwords did not match.');
}
}
}
}
password is the old password, passwordn is the new one, password2 is the new one retyped.
Is there any other, more coomon way to do it in cake?
I see that you validate and manipulate data in the controller. Doing this in a model is generally a better practice. I implemented similar functionality just a few days ago. My change_password() method looks somewhat like this:
# app/controllers/users_controller.php
function change_password() {
if (!empty($this->data)) {
if ($this->User->save($this->data)) {
$this->Session->setFlash('Password has been changed.');
// call $this->redirect() here
} else {
$this->Session->setFlash('Password could not be changed.');
}
} else {
$this->data = $this->User->findById($this->Auth->user('id'));
}
}
And here's a stripped down version of the view used with that method:
# app/views/users/change_password.ctp
echo $this->Form->create('User');
echo $this->Form->input('id');
echo $this->Form->input('current_password');
echo $this->Form->input('password1');
echo $this->Form->input('password2');
echo $this->Form->end('Submit');
The code that does something interesting is in the model. I added the fields from the form to the validate property and wrote custom validation methods. This allows me to use password1 and password2 fields in any other place in the application, for example, on the registration form.
# app/models/user.php
var $validate = array(
'current_password' => array(
'rule' => 'checkCurrentPassword',
'message' => '...'
),
'password1' => array(
'rule' => 'checkPasswordStrength',
'message' => '...',
),
'password2' => array(
'rule' => 'passwordsMatch',
'message' => '...',
)
);
Finally, in the beforeSave() callback of the model I set password to the hash of password1 to prepare the data to be stored it in the database.
The solution provided by Mike is great, but he left out the "checkCurrentPassword" function. Here is an example of that function you can place in your Model:
# app/models/user.php
public function checkCurrentPassword($data) {
$this->id = AuthComponent::user('id');
$password = $this->field('password');
return(AuthComponent::password($data['current_password']) == $password);
}
This solution gets the current user ID from the Auth component and changes the model to point to that particular user. Then it compares the hash of the current_password entered on the form with the hashed password stored for that user.
Also, here is the beforeSave function you can use to hash the new password:
# app/models/user.php
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password1'])) {
$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password1']);
}
return true;
}
You can use the latest version of the cakeDC plugin. This plugin gives you all functionality of all functions related to login, logout, reset password, change password, etc. You can find the latest version here.
You can simply use the:-
Step1)
$password = $this->Auth->password($this->data['User']['password']); // It will generate the hashed password using the cakephp's Auth component.
Step2)
if($this->User->update(array('User.password'=>$password), array('User.id'=>$this->Auth->User('id')))) {
echo $this->Session->setFlash('Password changed successfully.', 'default',
array('class'=>'successMsg'));
}

Resources