The jist of this question is about how to override CakePHP's auth component login function to log a user in based on something other than the default username and password.
So, we're developing a custom login function for one of our partners. Basically, the solution provides online courses to a number of companies who want to provide their clients and/or employees with in house training material.
This particular solution takes a home loan account number and personal identification number and does some algorithm validation and logs the user in. Or at least - that's what it should do.
Currently, the auth component tries to user a particular model to compare username and hashed password. Is there anyway to override this particular behaviour and get the Authcomponent to log the user in using the algorithm (glorified regex check) in a custom function? It should completely ignore the need for a username and password. In addition we won't actually have these account numbers and ID numbers stored anywhere. They will each be checked for certain related patterns.
Cheers
Custom your login with authenticate property :
In your appController
public $components = array(
'Session',
'Auth' => array(
'authError' => "You don't have accès",
'authorize' => array('Controller'),
authenticate' => array(
'Form' => array('userModel' => 'MyUserModel',
'fields' => array('username' => 'numberuser','password' => 'personnalId'),
)
)
);
Related
I was stuck in this problem for almost a week. Even going through all the solutions in the internet doesn't help me to solve my problem. So I decided to ask my problem here and hope I will get solution.
My problem is why i always get Auth FALSE even my password is correct? I manage to add a new user and it stores the encrypted password, but when I try to login using that username and `password, it displays "Invalid username or password, try again"
Here is my code.
Thank in advance
I had some troubles with this part of cake as well, here is the way that I ended up using which works for me
public function login() {
if ($this->request->is('post')) {
$options = array(
'conditions' => array(
'User.username' => $this->request->data['User']['username'],
'User.password' => Security::hash($this->request->data['User']['password'], 'sha256', true)
),
'fields' => array(
'User.id',
'User.username',
'User.group_id',
// other fields you need
)
);
$userData = $this->User->find('first', $options);
if (!empty($userData) && $this->Auth->login($userData['User'])) {
$this->redirect($this->Auth->redirectUrl());
} else {
$this->Session->setFlash(__('Username, and/or password are incorrect'));
}
}
}
so, the bottom line is to manually check if the user with the given username and password exists, and then login them. Btw, pls pay attention that to login() function I am passing an array like this
array(
'id' => 1,
'email' => 'test#gmail.com',
'group_id' => 2
);
and not
array(
'User' => array(
'id' => 1,
'email' => 'test#gmail.com',
'group_id' => 2
)
);
also, you do not want to pass the password to the login function if you are using cake 2.x, from cake docs
http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#identifying-users-and-logging-them-in
In 2.x $this->Auth->login($this->request->data) will log the user in
with whatever data is posted, whereas in 1.3
$this->Auth->login($this->data) would try to identify the user first
and only log in when successful.
so, first of all no need to pass the password, but you should be careful to check the valid user password before using login because if as it mentioned above, cake will login the user with any data.
About the password hashing part, please be sure you are using the same hashing algorithm with exact same salt (if you use it and you should) that you used during registration.
edit:
try using this in the model's beforeSave
public function beforeSave($options = array()) {
parent::beforeSave($options);
if (isset($this->data[$this->alias]['password'])) {
$this->data[$this->alias]['password'] = Security::hash($this->data[$this->alias]['password'], 'sha256', true);
}
}
For the last thing it is better practice to save a unique salt per user(salt should be saved in db along with user's password). In cake only one application side salt is being used for all passwords, and if the db and the app are compromised attacker can generate just one custom rainbow table for all passwords, but if the salt is unique for each user, then the bad guy should create custom rainbow table for each user separately, which is a lot more work/time/money.
After user login to the system, based on user_id, forward to their own dashboard, the url should be like ...../dashboard/userid,
the dashboard will display for example the latest orders which the user placed not others
I read How to build a “dashboard” for your application in CakePHP.
but it is same for all users
How can I map $userid to /dashboard?
Many thanks.
I don't think this is correct design. You should have a universal url for all user dashboards (/dashboard) and use the Auth component to retrieve the user_id and display based on that.
$user_id = $this->Auth->user('id');
$orders = $this->Order->findByUserId($user_id);
Fore redirection you could do:
$this->redirect(
array(
"controller" => "dashboard",
"action" => "index",
$user_id
)
);
Did you mean something like this
I started trying out CakePHP a few months ago and I'm now attempting to create a "change password page" for logged in users. I have a form consisting of these fields: current password, new password and new password confirmation. For the current password, I want to validate that it matches the password of the logged in user, as a rule within the user Model. I know that I can get information of the logged in user with this: AuthComponent::user(). However, it provides me every field of the model except the password.
I know that Auth->login() is responsible for setting the session variables for the logged in user, but I'm not sure what I'm doing wrong here that only the password field cannot be accessed:
public function login() {
if ($this->request->is('POST')) {
if($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash('Your username/password combination was incorrect.');
}
}
}
Here's my login view:
<h2>Login</h2>
<?php
echo $this->Form->create('Promoter');
echo $this->Form->input('username');
echo $this->Form->input('password', array('type' => 'password'));
echo $this->Form->end('Login');?>
I'm using the Promoter model as the user, which i set in the AppController:
public $components = array(
'Auth'=>array(
...
'authenticate' => array(
'Form' => array('userModel' => 'Promoter')
),
'authorize' => array('Controller')
)
);
I can resort to validating the password in the Controller, but that would be giving up :) Please tell me if I need to provide more code to clarify the issue.
Thanks.
You're probably not doing anything wrong, this is most likely a security feature. There is no reason to keep a password in your session.
Secondly, even if it was in session, it would be encrypted (or at least I hope so, if it's not you should change that immediately!). So you still couldn't do a simple comparison.
To compare the old password, you should query your Promoter model, and get the hashed password from there, then hash the old password from your "change password" form, and finally compare the hashed results.
Because cake doesn't store the password in the session:
lib/Cake/Component/Auth/BaseAuthenticate.php line 94
unset($user[$fields['password']]);
Please bear with me, as I'm a programming/cakephp noob, but I do not know how to restrict the user from access to other user data. When a user logs in, they get a dashboard of their listings (which come from several models....restaurants, hotels, golf courses, images for each listing, location information, coupons, etc..).
Depending on the model/controller, I could have user '3' (who has hotel listings) type into the browser bar /restaurants/edit/1 and edit the restaurant information of user '17' who has a restaurant with ID='1'. Worse, they can even access /users/dashboard/17. How do I limit a user to only access their own data? I was hoping there was some sort of 'beforeAllow()' part of the AuthComponent I could use in the AppController that checks user id beforehand and kicks them back out to their dashboard if they try to perform a CRUD action on other users' data.
Even if I was using ACL (I know I should but it's frankly a little too over my head at this stage of learning), I'd still have to know the right code to limit user access, correct?
Below is my AppController:
class AppController extends Controller {
public $components = array(
'Session',
'Auth' => array(
'loginRedirect' => array('controller' => 'users', 'action' => 'view'),
'logoutRedirect' => array('controller' => 'docs', 'action' => 'index'),
'authError' => 'Sorry, you are not authorized to view this page.'
)
);
function beforeFilter() {
$this->Auth->userModel = 'User';
$this->Auth->allow('join_now','debug','index', 'condos', 'houses', 'hotels_and_motels', 'print_all_coupons', 'print_coupon', 'search', 'golf', 'charters', 'events', 'nightlife', 'shopping', 'visitors_info', 'contact_us', 'view', 'results');
}
}
and here is a sample edit function (the edit function from my UnitsController):
function edit($id) {
$this->set('title', 'Edit your property');
$this->Unit->id = $id;
if (empty($this->request->data)) {
$this->request->data = $this->Unit->read();
} else {
if ($this->Unit->saveAll($this->request->data)) {
$this->Session->setFlash('Your property has been updated.', 'success');
}
}
}
I will say that every one of my db tables has a user_id field so the logged in user can be matched with the user_id of each model.
I thought that this SO question was what I was looking for but they ended up getting off on a tangent in it and never answered the original question the user asked.
If you where using cakes acl and auth, then no, you wouldn't have to write a bunch of code, checking the user ids for each action, but you'd have to write the code to tie together the acl's. You'd tell the Component that your controller and actions require acl privelages. And it doesnt the look ups in the aros and acos tables to make sure that your object requesting the content has the proper permissions.
I HIGHLY recommend you take a look at the tutorial and figure out how to get it to work
If you don't go that route, then you will have to add in the checking to every action that loads dependent content. Basically you'll, when a action is requested, you'll feature the object, then get the user associated to that object and check to see if the id of the user the same as the id as the requesting the object.
if ( $this->Unit->User->uid != $this->Session->User->uid ) {
throw new NotFoundException('Could not find that Unit');
} else {
...
}
The other thing you can do, for pages that are the same, but customized, is not use the url /user/dashboard/17 and instead just use /user/dashboard then in the dashboard action, pull the user id from the session data and load the profile for the user that is authenticated
I think you will need to use CRUD authorization for adding EditOwn actions. The answer described in CakePHP ACL Database Setup: ARO / ACO structure? handles much of the logic that you need.
Please note that the solution is still not complete and you will get access to actions that can render other users' data. Example: scaffold 'index' methods. To restrict access to other users' data here, you can modify the query to add filters based on User ID that you get from Session.
On my site, users have public profiles that can be accessed via http://mysite.com/vanity_url. I want to allow users to point their own domains to their profile page on my site. Just like Bandcamp does.
My Profile model has these two fields to deal with this: vanity_url, which is the normal username type of field; and a new custom_domain which is their own domains' name, eg, example.com.
This is what I have done so far, but I'm afraid it might not be the most elegant, safe, and efficient way to do it.
First, I made sure Apache's DocumentRoot is set to my app's webroot directory, so I can tell users to point their DNS to my site's IP.
Now, this is how the routing rules on my routes.php look like:
if (preg_match('/mysite\.com\.?$/', $_SERVER['SERVER_NAME'])){
// Normal routes when visitors go to my domain
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/pages/**', array('controller' => 'pages', 'action' => 'display'));
// Move all other actions to a separate '/app/' area
Router::connect('/app/:controller/:action/**');
Router::connect('/app/:controller/**');
// Handle profile URLs
Router::connect('/:profile/**',
array('controller' => 'profiles', 'action' => 'view'),
array('pass' => array('profile'), 'profile' => '[0-9a-zA-Z\-\_]+')
);
}
else{
// If visitors come via a URL different to mysite.com, I let
// the ProfilesController deal with it passing the current SERVER_NAME
// as a param to the 'view' action
Router::connect('/', array(
'controller' => 'profiles',
'action' => 'view',
$_SERVER['SERVER_NAME'], // 'url' param
true // 'customDomain' param
));
Router::redirect('/*', 'http://mysite.com');
}
And this is how the view action on the ProfilesController looks like:
public function view($url = null, $customDomain = false) {
if ($url){
// Find the profile by its vanity_url or its custom_domain
$findOptions = array(
'conditions' => $customDomain?
array('custom_domain' => $url) :
array('vanity_url' => $url)
);
if ($profile = $this->Profile->find('first', $findOptions)) {
$this->set('profile', $profile);
}
}
else throw new NotFoundException(__('Invalid profile'));
}
What problems could I face with this approach?
Also, does anyone know why Bandcamp asks users to create a CNAME instead of an A record to set up a subdomain? Am I missing something I should consider here?
Edit Somebody helped me figure that last bit out: It seems you can't easily use an CNAME record to point a naked domain to another. The main question is still open.
I did something similar using cake 1.3 a year ago
There are 4 major steps in this solution:
Have a Domain model and domains datatable that records all the possible custom domains and Views for your users to enter their custom domains
create domain checking code in beforeFilter in AppController and save User data to Session
the Controller action that is responsible for viewing the public profile needs to reference the same User data that is saved to Session
your users need to setup CNAME and A records correctly with their domain registrars
Step 1 Domain model and datatable
What I did was I had a table called domains
Each Domain contains the web address which I assumed to be just a http://....
Domain belongsTo User
User hasMany Domain
domains
==========
id web_address user_id main
the main is a tinyint which indicates whether this is the main url to be used since User hasMany Domain.
so what happens is the user needs to create a new record or more for Domain.
Step 2 Code in beforeFilter of AppController to figure out which public profile to display
Next, your code needs to be able to retrieve the User id based on the url submitted upon each http request.
The User from this point onwards refer to the User whose public profile is viewed. Do NOT confuse this with the loggedIn User, should you have one.
I suggest that you do this in your beforeFilter of AppController.
Something like this
$currentUser = $this->User->getByDomain(FULL_BASE_URL);
$this->Session->write('CurrentUser', $currentUser);
The code for getByDomain for User model is also an exercise for you. Should be easy enough given that I have explained the schema for Domains
You may need to check against the currentUser inside your Session data before writing the currentUser because you do not wish to write the Session data all the time especially when the visitor is visiting the same webpage again and again.
You may need to change the above code snippet to something like this:
$currentUser = $this->Session->read('CurrentUser');
if(empty($currentUser) OR (!$this->checkUrlAgainstDomain(FULL_BASE_URL, $currentUser['Domain']['domain']))) {
$currentUser = $this->User->getByDomain(FULL_BASE_URL);
$this->Session->write('CurrentUser', $currentUser);
}
Again the function checkUrlAgainstDomain is an exercise for you.
Step 3 Code in beforeFilter of AppController to figure out which public profile to display
You will use the CurrentUser data saved in your Session to determine which User's public page you want to display.
I leave this as an exercise for you to figure out in your UsersController under action view
Step 4 your users need to enter the following in their domain registrars
Then your users need to go to their domain registrars and do a similar thing as BandCamp users in the faq you have linked to.
User's "www" subdomain should point using CNAME to example.Lucho.com
User's root domain (without the www) should have an A Record pointing to IP Address of your server ie where your Lucho.com is residing in
User must add both both of these Domains management page as explained earlier on the top. It can take up to 48 hours for the domain to completely update.
I guess there's no better way :P