CakePHP 2.3.2 BasicAuthentication not working - cakephp

I tried out the "Simple Acl controlled Application 1&2" tutorial located at http://book.cakephp.org/2.0/en/tutorials-and-examples/simple-acl-controlled-application/simple-acl-controlled-application.html .
After doing this, I tried to activate BasicAuth instead of FormAuth.
I reimplemented the login() function im my UsersController as follows:
public function login() {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash('Not able to login');
}
}
and changed the $components variable in my AppController to the following:
public $components = array(
'Acl',
'Auth' => array(
'authorize' => array(
'Actions' => array('actionPath' => 'controllers')
),
'authenticate' => array('Basic')
),
'DebugKit.Toolbar',
'Session'
);
The BasicAuth "popup" appears as expected, but when I'm trying to login, it reappers in an endless loop. I did not change anything after doing the tutorial except for including DebugKit.
What am I missing? I hope someone can help me, as I'd like to go with CakePHP coding my next Project!
Update
AppController
public function beforeFilter() {
//Configure AuthComponent
$this->Auth->allow('display');
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
$this->Auth->loginRedirect = array('controller' => 'posts', 'action' => 'add');
}
UsersController
public function beforeFilter() {
parent::beforeFilter();
}
I'm trying to access e.g. /users/ which works like a charm using the FormAuth described in the tutorial, so there can't be a permission problem. Logindata is pretty simple for testing (admin:admin) so there should be no problem either.
Update 2
In my Apache Log i get the following, so it says I'm not authorized:
IP - - [16/Apr/2013:18:08:37 +0200] "GET /users/login HTTP/1.0" 401 5179 "-" "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:23.0) Gecko/20130414 Firefox/23.0"
Update 3
For some reason it seems, that User and Password are either not sent or not saved in PHP. If I rewrite /lif/Cake/Controller/Auth/BasicAuthenticate to the following, it works!
public function authenticate(CakeRequest $request, CakeResponse $response) {
$_SERVER['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_PW'] = "admin";
$result = $this->getUser($request);
if (empty($result)) {
$response->header($this->loginHeaders());
$response->statusCode(401);
$response->send();
return false;
}
return $result;
}
Update 4
Don't know if thats helpful, but the Server is running Plesk 11, latest update, no special modifications.
Update 5
Okay, that answer of "thaJeztah" was useful, but now I'm getting more problems which can be subdivided.
Changed mode from fcgid to apache module
1.1. Results in working login, but logout does not work! After the redirect, the session seems to be cleared, but i can still access every restricted page until i clear my browsers "Active Logins" as it is called in Firefox.
var_dump($this->Session->read('Auth.User'));
NULL
When I access /users/login I am automatically logged in and redirected without having to enter login credentials.
print "<pre>";
print_r($this->Session->read('Auth.User'));
print "</pre>";
Array
(
[id] => 1
[username] => admin
[group_id] => 1
[created] => 2013-04-12 12:54:26
[modified] => 2013-04-16 14:27:24
[is_active] => 1
[Group] => Array
(
[id] => 1
[name] => Admin
[created] => 2013-04-12 12:46:42
[modified] => 2013-04-12 12:46:42
)
)
Using the .htaccess based solution works as well, it even looks like as if thats the only change needed (I removed the list() code as I did never get into it and it worked as well).
2.1. Same problem as above, no real logout possible.
Update 6
Probably the last or one of my last updates. :-)
Right now I'm trying to do a "fake logout" by logging the user in as a guest user I created who has only access to /users/login and /pages/home: http://guest:guest#my.domain/users/login
Accessing /users/logout might work too, as I'm using this piece of code there:
public function logout() {
$user = $this->User->find('first', array('conditions' => array('username' => 'guest')));
$this->Auth->login($user['User']['id']);
}
I simly don't believe, this will be consistent, since I believe the Session data will be deleted some time and the browser still got the active admin login and authenticates using these - am I right?
After that I can login a different User again using http://admin:admin#my.domain/users/login. Not perfect, but works at least for Firefox.
So basically one last question: Any suggestions on how to force a BasicAuth when accessing /users/login? This way I could easily switch users at any time using any client.
Update 7
I found a way to do exactly this with the idea in my accepted answer. I hope I caught all edge cases in this, feel free to correct me if not!
(P.s.: when using ACL and or basic authentication the isAuthorized() in at least the AppController seems to be ignored (it was recognized, but had no effect - when i deleted the method without changing $components, i got an error) which lead to me implementing this without using isAuthorized().)
AppController.php
public function beforeFilter($redirectlogin = true) {
//Configure AuthComponent
$this->Auth->allow('display', '/users/login');
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'pages', 'action' => 'home');
$this->Auth->loginRedirect = array('controller' => 'pages', 'action' => 'home');
$this->Auth->unauthorizedRedirect = array('controller' => 'HTTPCODE', 'action' => 'c403');
if($redirectlogin && $this->Session->read('Auth.needs_reauthenticate')) {
if(!($this->request->params['controller'] == $this->Auth->loginRedirect['controller'] && $this->request->params['pass'][0] == $this->Auth->loginRedirect['action'])) {
$this->redirect('/users/login');
}
}
}
UsersController.php
public function beforeFilter() {
parent::beforeFilter(false);
}
public function login() {
$this->autoRender = false;
$this->Session->write('Auth.needs_reauthenticate', true);
if(!$this->Session->check('Auth.count')) {
$count = 1;
} else {
$count = $this->Session->read('Auth.count') + 1;
}
$this->Session->write('Auth.count', $count);
if($this->Session->read('Auth.needs_reauthenticate')) {
if((isset($_SERVER['HTTP_AUTHORIZATION']) && $this->Session->read('Auth.count') == 1) || (!isset($_SERVER['HTTP_AUTHORIZATION']) || empty($_SERVER['HTTP_AUTHORIZATION']) || !$this->Session->check('Auth.sent_header_step') || $this->Session->read('Auth.sent_header_step') < 1)) {
unset($_SERVER['HTTP_AUTHORIZATION']);
$this->Session->write('Auth.redirectTo', $this->Auth->redirect());
$this->response->header(sprintf('WWW-Authenticate: Basic realm="%s"', env('SERVER_NAME')));
$this->response->statusCode(401);
$this->response->send();
$this->Session->write('Auth.sent_header_step', 1);
}
if(isset($_SERVER['HTTP_AUTHORIZATION'])) {
$this->Session->write('Auth.sent_header_step', 0);
$base64string = base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6));
if(!(strlen($base64string) > 1 && substr($base64string, -1, 1) != ":")) {
$_SERVER['PHP_AUTH_USER'] = "";
$_SERVER['PHP_AUTH_PW'] = "";
}
$data = true;
}
$this->Auth->logout();
if(isset($data) && $this->Session->read('Auth.count') > 1) {
if($this->Auth->login()) {
$this->Session->write('Auth.needs_reauthenticate', false);
if($this->Session->check('Auth.redirectTo')) {
$redirectTo = $this->Session->read('Auth.redirectTo');
$this->Session->delete('Auth.redirectTo');
$this->Session->delete('Auth.count');
return $this->redirect($redirectTo);
} else {
return $this->redirect($this->Auth->redirect());
}
} else {
$this->response->statusCode(403);
// my 403 message
}
} else {
if(!isset($_SERVER['HTTP_AUTHORIZATION']) && $this->Session->read('Auth.count') > 1 && isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']) && trim($_SERVER['PHP_AUTH_USER']) != "" && trim($_SERVER['PHP_AUTH_PW']) != "") {
if($this->Auth->login()) {
$this->Session->write('Auth.needs_reauthenticate', false);
if($this->Session->check('Auth.redirectTo')) {
$redirectTo = $this->Session->read('Auth.redirectTo');
$this->Session->delete('Auth.redirectTo');
$this->Session->delete('Auth.count');
unset($_SERVER['HTTP_AUTHORIZATION']);
unset($_SERVER['PHP_AUTH_USER']);
unset($_SERVER['PHP_AUTH_PW']);
return $this->redirect($redirectTo);
} else {
return $this->redirect($this->Auth->redirect());
}
} else {
$this->response->statusCode(403);
// my 403 message
}
}
$this->response->statusCode(403);
// my 403 message
}
}
}
Thanks in advance
Adrian

Using Basic Authentication when running PHP as (Fast)CGI
It's possible that your website is configured to run PHP as (Fast)CGI, in which case the PHP_AUTH_USER and PHP_AUTH_PWD keys are not present in the $_SERVER variable. The BasicAuthenticate AuthComponent relies on these keys.
Either change the domain/webhosting settings in Plesk to run php as 'apache module' for this website/domain or extend the BasicAuthenticate Component to get these variables some other way.
More information on this subject can be found in this question:
PHP_AUTH_USER not set?
And for the Symfony framework, somebody seems to have written a workaround that may be useful in this situation as well;
https://github.com/symfony/symfony/issues/1813
update: loging out when using basic authentication
Loging out when using basic authentication is not really possible. Basic authentication is a 'stateless' authentication mechanism, which basically means that the browser is sending the user-credentials with every request. In other words; the server does not keep a 'state', the browser does. With Basic Authentication, you require the browser to send user credentials and as long as the browser sends valid credentials, you allow the browser access to the protected pages.
The only way to log out, is to close the browser, or tell the browser to close active sessions/logins.
Read more information here:
http://en.wikipedia.org/wiki/Basic_access_authentication
http basic authentication "log out"
Notes
Base Authentication is not a secure authentication mechanism; the username and password is sent to the server with every request. The password is sent unencrypted (only base64 encoded to prevent problems with special characters).
Although Form authentication also sends the password unencrypted, it is (a bit more) secure as it will only send the username/password when logging in. Subsequent requests will only send the Session-id, which can be set to expire and limited to a specific IP and/or Browser type.
In all cases, securing the connection via SSL is obviously important.
Forcing re-authentication on the login page
This is just 'thinking out loud', untested and highly experimental :)
Try this;
If no session is active, proceed the normal way. There is no way to differentiate 'already logged in' users from 'new users' in Basic Authentication - it is stateless
If a session is active, a user apparently has an active session going on. Don't destroy the session, but change the rules;
If the credentials sent are for the same user as the username inside the session (or, better: $this->Auth->user('username');?, Then invalidate the session (not destroy) and force the user to re-authenticate, by sending login headers;
You may copy the headers from the BasicAuthenticate behavior; see the source here BasicAuthenticate::authenticate()
Regarding 'copying the headers'; Maybe extending the BasicAuthenticate is a cleaner approach; handle all your custom code inside your customized version.
Additionally, check if the session is still 'valid' inside AppController::isAuthorized()(see Using ControllerAuthorize)
Something like this (Mockup code):
Login page/action:
if ("usercredentials sent by browser" === "current logged in user in session") {
// Mark session as 'needs-to-reauthenticate'
$this->Session->write('Auth.needs_reauthenticate', true);
// Need to find a clean approach to get the BasicAuth loginHeaders()
// *including* the right settings (realm)
$this->response->header(/*BasicAuth::loginHeaders()*/);
// Access denied status
$this->response->statusCode(401);
return $this->response->send();
}
AppController::isAuthorized()
if ($this->Session->read('Auth.needs_reauthenticate')) {
return false;
} else {
// Normal 'isAuthorized()' checks here
}
NOTE:
Once a browser has visited the 'login' page during an active session, the user will either have to log-in with different credentials, or close the browser to log in again.
This may be problematic if the session-cookie is still present after closing and re-opening the browser. Try to force the session-cookie to be a "real" session-cookie and have it deleted on browser close by setting Session.cookieTimeout to 0 (see Session Configuration

Related

Cakephp redirect in isAuthorized method not working

So after login in isAuthorized method I'm trying to redirect user based on a condition. But problem is it's not redirecting. Below the code that I have tried.
protected function isAuthorized($LoginUser)
{
if ($this->getTable('Users')->hasCompany($LoginUser) == false){
$this->redirect(['controller'=>'Companies','action'=>'edit']);
dd("hello");
}
}
It's not redirecting and getting hello message. How can I redirect after login user to another page based on condition ?
As mentioned in the comments, the auth component's authorization objects are supposed to return a boolean, and depending on that, let the component do the unauthorized handling-
What you could do, is for example dynamically set the component's unauthorizedRedirect option (and probably also authError) from the controller's authorization handler for that specific case (I guess you'd also have to exclude the respective company controller's action from that check, as otherwise you'll end up in an infinite redirect loop):
if (!$this->getTable('Users')->hasCompany($LoginUser)) {
$message = __('You must provide company information in order to proceed.');
$url = \Cake\Routing\Router::url([
'controller' => 'Companies',
'action' => 'add'
]);
$this->Auth->setConfig([
'authError' => $message,
'unauthorizedRedirect' => $url,
]);
return false;
}
// ...
return true;
If you find yourself in a situation where there's no such possibility, brute forcing a redirect by throwing a \Cake\Http\Exception\RedirectException could be a solution too, even though it's ideally avoided, it's better than dying, as it will at least emit a clean redirect response:
$url = \Cake\Routing\Router::url([
'controller' => 'Companies',
'action' => 'add'
]);
throw new \Cake\Http\Exception\RedirectException($url);
See also
Cookbook > Controllers > Components > AuthComponent > Configuration options

Authorized public URL keeps redirecting for authentication and failing

In this scenario, OurCustomAuth is currently returning an expected value of false, is reaching the appropriate else, but the users/error path keeps redirecting even though it's been made public and not requiring any authentication.
I've setup the new action:
C:\wamp\myapp\app>Console\cake AclExtras.AclExtras aco_update
Welcome to CakePHP v2.4.9 Console
---------------------------------------------------------------
App : app
Path: C:\wamp\myapp\app\
---------------------------------------------------------------
Created Aco node: controllers/Users/error
Aco Update Complete
In the UsersController, I've added the action to be made public:
public function beforeFilter() {
parent::beforeFilter ();
$this->Auth->allow ('logout', 'error');
}
In AppController, the Auth config:
public $components = array(
'Acl',
'Cookie',
'DebugKit.Toolbar', 'Session',
'Auth' => array(
'authenticate' => array('OurCustomAuth'),
'loginAction' => array('controller' => 'users', 'action' => 'view'),
'authError' => 'Did you really think you are allowed to see that?',
'authorize' => array('Actions' => array('actionPath' => 'controllers'))
)
);
...
public function beforeFilter() {
...
//Auto logging users in if they are not logged in
if (!AuthComponent::user('id')) {
if ($this->Auth->login()) {
//stuff here
} else {
$this->Session->setFlash(__('We could not authenticate you ...'));
return $this->redirect(array('controller' => 'Users', 'action' => 'error'));
}
}
...
}
The error I get in Firefox:
The page isn’t redirecting properly
Firefox has detected that the server is redirecting the request for
this address in a way that will never complete.
Update #1
$this->Auth->login() essentially grabs request headers, that in this case are intentionally wrong, which seems to redirect to the appropriate link. However, /users/error shouldn't cause a redirect as it's excluded from Authentication.
The problem is that you run your login code on every request, ie in the app controllers beforeFilter() method. So when that code redirects you to /users/error because you're not logged in, the code will run again for that controller/action, and redirect you again, and again, and again...
If you need to run this code for every request, then you'll have to check the allowed actions manually, ie the actions allowed via $this->Auth->allow(), and run your code only in case the current action isn't allowed. Check the code of AuthComponent::_isAllowed(), you can easily use that with minimal modifications:
$action = strtolower($this->request->params['action']);
if (!in_array($action, array_map('strtolower', $this->Auth->allowedActions))) {
//Auto logging users in if they are not logged in
if (!AuthComponent::user('id')) {
// ...
}
}

Cakephp Auth->loginredirect Problems

i have made a simple cakephp application . at the moment i am just working with auth component
to send user to their respective pages according to their. for ex if role =1 send to admin page and else if role = 2 send it to moderator page . i am using both session and auth component to see how they work and save data in them. below is the code for usercontroller login action
public function login(){
$this->Session->setFlash($this->Auth->user('role'));//checks for data in auth component if any
if($this->request->is('post') ){
$results = $this->User->findByEmail($this->request->data['User']['username']);
if($results &&$results['User']['password']== md5($this->request->data['User']['password']))
{
$this->Session->write('user',$results['User']);
$this->Auth->login($results['User']);
$this->Session->setFlash('User logged in successfully'.$this->Auth->user('role'));
return $this->redirect($this->Auth->redirect());
}
else
{
$this->Session->setFlash('Login is incorrect');
}
}
}
The problem is the login works fine all the data is stored in session and auth variable but loginredirect behave weird. in my chrome browser . it always redirects to admin page no matter what the role is , but it is flashing correct message which i set in flash. the code of beforefilter in appcontroller
public function beforeFilter(){
$this->Auth->allow('display');
$this->Auth->loginAction = array('controller' => 'Users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'Users', 'action' => 'login');
if($this->Auth->user('role') == '1'){
$this->Session->setFlash($this->Auth->user('role').'adminnnnnnnnnnnnnnnnnnnnn');
$this->Auth->loginRedirect = '/admins/index';
}
if($this->Auth->user('role') == '2'){
$this->Session->setFlash('moderatorrrrrrrrrrrrrrrrr');
$this->Auth->loginRedirect = '/users/index';
}
}
so the problem is the loop runs fine in before filter , the setflash display whether user is admin or moderator , but for some reason it redirects to only single page either admins/index page or users/index page no matter who logs in . This is behavior on chrome browser.
On firefox the loginredirects sends user to webroot/index page but again the flash messages are correct.
I am not sure what i am doing wrong is there a problem in my code or cakephp 2.0 auth component has measure bugs.
after user logs in it gets redirected via Auth->loginRedirect to dashboard() and here i check users role and use redirect to send particular user to the exact location
function dashboard() {
//get user's group (role)
//$role = $this->Session->read('user.role');
$role=$this->Auth->user('role');
//user selection logic here
if($role== '1'){
$this->redirect(array('controller' => 'users','action' => 'admin_index','admin' => false));
}
else if($role == '2'){
$this->redirect(array('controller' => 'users','action' => 'admin_index', 'admin' => false));
}
else if($role == '9'){
$this->redirect(array('controller' => 'users', 'action' => 'index', 'admin' => false));
$this->Session->setFlash('3');
}
}
This is just another way to work things out i included the dashboard function in my users controller and did auth login redirect to this function from appcontroller.
Hope it solves problem for others who are facing the issue. Thanks

CakePHP 2.x Cannot prevent deleted and banned user from logging in

I have a bit of code that prevents deleted and banned users from logging in. To clear minds a status of -2 means that the user is deleted, and -1 means that the user is banned. Below is the code which works fine on local, but on the live it sucks. users who have status of -1 or -2 are still able to login. I cannot find where the problem is.
if ($this->Auth->login()) {
//first check if the user's status is -1 or -2.
$status = $this->Auth->user('status');
if ($status == '-1') {
$this->Auth->logout();
$this->Session->setFlash(__('This account has been banned. Please contact with us.'));
$this->redirect('/');
} elseif ($status == '-2') {
$this->Auth->logout();
$this->Session->setFlash(__('This account has been deleted, and is not usable anymore.'));
$this->redirect('/');
}
//something else
}
By calling $this->Auth->login() in the check you are logging the user in.
You could avoid this, and check the user information prior to login, or you could add the status flags to the scope for users.
$this->Auth->authenticate = array(
AuthComponent::ALL => array(
'userModel' => 'User',
'scope' => array('User.status' => '> 0)
),
'Form',
'Basic'
);
This add the status field check to the login process.
If you want to customise messages as in your example, you can check the values on the user information before processing login:
$user = $this->User->findByUsername($this->data['User']['username']);
if (!empty($user)) {
if ($user['User']['status'] == -1) {
// Set message for banned account
}
if ($user['User']['status'] == -2) {
// Set message for deleted account
}
$this->redirect( ... ); // Redirect away
}
if ($this->Auth->login()) {
// Normal login process
}
The scope answer is the best, but there is a small error in the condition, instead of
'scope' => array('User.status' => '> 0)
the line should read
'scope' => array('User.status >' => 0)
which is the typical cakephp way to add comparison operators in condition arrays
(I would have just commented this, but it is easier to create an answer when new to this site.)

CakePHP + Facebook

I am trying to implement facebook Connect to my cakephp Application. i am using Nick's Facebook Plugin.
I wanna implement it this way
When a user Visits the Site he should be able to login via Registration on the site or Facebook Connect
Existing users should be able to connect their account to their FB account
People who first time login to the site using FB Connect and dont have an account on the site. should be redirected to a page where they have to enter details to complete the profile.
What i have done -
I have followed the instruction of Nick to implement it and when i click Login - it connects to my app. but i dont understand how to create a username and password associated with the Fb Connect Id. and user it against the FB token.
Apparently I'm doing the same thing a little before you... ;-)
Here's a method for Facebook login I'm using (slightly redacted and annotated):
public function facebook($authorize = null) {
App::import('Lib', 'Facebook.FB');
$Fb = new FB();
$session = $Fb->getSession();
// not logged into Facebook and not a callback either,
// sending user over to Facebook to log in
if (!$session && !$authorize) {
$params = array(
'req_perms' => /* the permissions you require */,
'next' => Router::url(array('action' => 'facebook', 'authorize'), true),
'cancel_url' => Router::url(array('action' => 'login'), true)
);
$this->redirect($Fb->getLoginUrl($params));
}
// user is coming back from Facebook login,
// assume we have a valid Facebook session
$userInfo = $Fb->api('/me');
if (!$userInfo) {
// nope, login failed or something went wrong, aborting
$this->Session->setFlash('Facebook login failed');
$this->redirect(array('action' => 'login'));
}
$user = array(
'User' => array(
'firstname' => $userInfo['first_name'],
'lastname' => $userInfo['last_name'],
'username' => trim(parse_url($userInfo['link'], PHP_URL_PATH), '/'),
'email' => $userInfo['email'],
'email_validated' => $userInfo['verified']
),
'Oauth' => array(
'provider' => 'facebook',
'provider_uid' => $userInfo['id']
)
);
$this->oauthLogin($user);
}
This gives me an array with all the user details I could grab from Facebook and invokes ::oauthLogin, which either logs the user in with the given information or asks the user to fill in missing details and/or creates a new user record in the database. The most important part you get from the Facebook API is the $userInfo['id'] and/or email address, either of which you can use to identify the user in your database. If you're using the AuthComponent, you can "manually" log in the user using $this->Auth->login($user_id), where $user_id is the id of the user in your own database.
private function oauthLogin($data) {
$this->User->create();
// do we already know about these credentials?
$oauth = $this->User->Oauth->find('first', array('conditions' => $data['Oauth']));
if ($oauth) {
// yes we do, let's try to log this user in
if (empty($oauth['User']['id']) || !$this->Auth->login($oauth['User']['id'])) {
$this->Session->setFlash('Login failed');
}
$this->redirect('/');
}
// no we don't, let's see if we know this email address already
if (!empty($data['User']['email'])) {
$user = $this->User->find('first', array('conditions' => array('email' => $data['User']['email'])));
if ($user) {
// yes we do! let's store all data in the session
// and ask the user to associate his accounts
$data['User'] = array_merge($data['User'], $user['User']);
$data['Oauth']['user_id'] = $user['User']['id'];
$this->Session->write('Oauth.associate_accounts', $data);
$this->redirect(array('action' => 'oauth_associate_accounts'));
}
}
// no, this is a new user, let's ask him to register
$this->Session->write('Oauth.register', $data);
$this->redirect(array('action' => 'oauth_register'));
}
Look no further. Here is an excellent article that'll guide you all the way through (minus any readymade plugins):
Integrating Facebook Connect with CakePHP's Auth component
Simply follow the approach described in there.
Cheers,
m^e

Resources