Cakephp redirect in isAuthorized method not working - cakephp

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

Related

CakePHP how to redirect to a page after login

So I made the application so every time I try to visit a page on it it redirects me to login, thats good, but after I log in I want it to redirect me to a certain page, but it doesnt, it just sends me back to page it redirected me from. I used the code from CakePHP cookbook for login:
public function login()
{
$this->request->allowMethod(['get', 'post']);
$result = $this->Authentication->getResult();
if ($result->isValid()) {
$redirect = $this->request->getQuery('redirect', [
'controller' => 'Buslines',
'action' => 'index',
]);
return $this->redirect($redirect);
}
if ($this->request->is('post') && !$result->isValid()) {
$this->Flash->error(__('Invalid username or password'));
}
}
Now all I need to know is how do I redirect to buslines/index after login.
You are explicitly requesting the redirect query variable and use it for redirection, so what you're seeing is the expected behavior, as that query variable contains the previously visited endpoint where access was denied. The second argument, the default value, will never be used unless the redirect query variable somehow gets lost.
Note that this redirect is only for when authentication was successful, so if you always want to redirect to a fixed endpoint after successful authentication, then just pass the corresponding value directly to redirect(), eg:
return $this->redirect([
'controller' => 'Buslines',
'action' => 'index',
]);
You also don't have to configure the authentication service's queryParam option then.

CakePHP Auto redirect Admin 404 to login

I am using the Auth component and ACL to give permission to roles and redirect them to /login if they don't have the proper rights.
That works fine for all existing controller/action. But when ever I enter a none existing controller action http://www.mypage.de/fake/bla I get a notFoundExeption.
My issue with that is, that the user sees my admin layout, because the 404 is rendered within the admin layout template.
Is there a way to say that admin (or any prefix) is always restricted and users that are not logged in get redirected to the login page?
What I came up with is this, but I don't like it, because I would have to do the same for all other prefixes.
/**
* AppController::afterFilter()
*
* #return void
*/
function afterFilter() {
if ($this->response->statusCode() === 404 && !empty($this->request->params['admin'])) {
$url = Router::url(
array(
'admin' => false,
'plugin' => false,
'controller' => 'users',
'action' => 'login'
)
);
$this->Common->flashMessage(__('You are not authorized to access that location.'), 'error');
return $this->redirect($url);
}
}
Your code actually does the trick pretty well, but there is no way to automatically do this for every prefix, except maybe doing it in a foreach loop:
function afterFilter() {
if($this->response->statusCode() === 404) {
foreach(array('admin', 'otherprefix') as $prefix) {
if(!empty($this->request->params[$prefix])) {
[...]
}
}
}
}
If you want to treat all 404 errors the same – regardless of the possible prefixes – you could just change the layout for your 404 error:
function afterFilter() {
if($this->response->statusCode() === 404) {
$this->layout = 'default';
}
}
This way, no one would see the admin layout on any 404 page.

Cakephp 3 redirect controller

In my CakePHP 3 application, after a controller redirect I get this error:
Error: [LogicException] Controller action can only return an instance of Response
Request URL: /mycontroller/
Stack Trace:
#0 /var/www/vhosts/example.com/httpdocs/vendor/cakephp/cakephp/src/Routing/Dispatcher.php(87): Cake\Routing\Dispatcher->_invoke(Object(App\Controller\SigninsController))
#1 /var/www/vhosts/example.com/httpdocs/webroot/index.php(37): Cake\Routing\Dispatcher->dispatch(Object(Cake\Network\Request), Object(Cake\Network\Response))
#2 {main}
Regarding to CakePHP 2 controller documentation redirect was like this:
$this->redirect('/orders/thanks');
$this->redirect('http://www.example.com');
return $this->redirect(
array('controller' => 'orders', 'action' => 'confirm')
);
But in CakePHP 3 documentation it seems like this:
return $this->redirect('/orders/thanks');
return $this->redirect('http://www.example.com');
return $this->redirect(
['controller' => 'Orders', 'action' => 'thanks']
);
When I add return word before $this->redirect, error is solved. So does this makes problem ? Because I couldn't see a return redirect part in cakephp 3 migration guide. Migration guide only mentions that third parameter is dropped.
Return in CakePHP 3 application is used when you want to redirect to other pages or we can say actions/methods of other controllers or even if you want to redirect to 3rd Party Links like you have specified :-
return $this->redirect('/orders/thanks');
return $this->redirect('http://www.example.com');
return $this->redirect(['controller' => 'Orders', 'action' => 'thanks']);
And if you want to redirect to other method in the same controller then you can leave the return and use setAction as below but here URL will remain the same.
$this->setAction('thanks');
Return is better approach and URL will redirect if you use below code.
return $this->redirect(['action' => 'thanks']);
Take a look at this
Response provides an interface to wrap the common response-related tasks such as:
Sending headers for redirects.
and this
You should return the response instance from your action to prevent view rendering and let the dispatcher handle actual redirection.
if you are using route name then use this-
$this->redirect(['_name' => 'view-details']);
you are using route then use this-
$this->redirect('/page/view-details');
and last option is this
$this->redirect(['controller' => 'Pages', 'action' => 'view']);
----------------------------------------------------------------------
if you are use route name in view file then this use-
use Cake\Routing\Router;
echo Router::url(['_name' => 'view-details']);
echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' => 15]);
In cakephp, 3.6 to go to specific view/page Use $this->render('filename');
like below
public function search()
{
$this->render();
$this->render('profile');
}
You need to turn off Autorendering
since cakephp methods by default want to end with a redirect to a CTP file
use $this->autoRender = false
inside you function

CakePHP 2.3.2 BasicAuthentication not working

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

Cakephp Auth Component - Home page redirect loop

I want to have a login form in my home page, the registered users should be redirected to users/index
with the below code, my home page is going to redirect loop
can anyone tell me where is the issue ??
Note:- infact it is perfectly working if i change the line to
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
App Controller
public function beforeFilter(){
$this->Auth->autoRedirect = false;
$this->Auth->loginAction = array('controller' => './', 'action' => 'index');
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => './', 'action' => './');
$this->Auth->authorize = 'controller';
$this->Auth->authError= 'You need permissions to access this page';
$this->Auth->allow('index');
$this->set('Auth',$this->Auth);
}
UsersController
public function login(){
$id = $this->Auth->user('id');
if(empty($id)){
if($this->request->is('post')){
if($this->Auth->login()){
$this->redirect($this->Auth->redirect());
}else{
$this->Session->setFlash('Invalid Username or password');
}
}
}else{
$this->redirect(array('action'=>'index'));
}
}
Thanks for the help...
You pretty much answered your own question here:
Note:- infact it is perfectly working if i change the line to
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
Indeed that would work and that is what it should look like. Right now, you're telling the auth component your loginAction (the action which holds your login logic) is the index action of the ./ controller (which doesn't even exist). I'm assuming you're confusing it with the loginRedirect variable, which is for setting the page to go to after successful authentication.
If you only want Registered Users to Access Your Site you could have something like this... at least, this is how I implement something similar in my site...
In your app_controller file add the following to the beginning of your beforeFilter() function
function beforeFilter(){
//Check if user was able to log in thru Auth using your form in the homepage
if($this->isLoggedIn() == TRUE){
$this->layout = 'default'
}else{
// You can created this layout with a login form and
// whatever else you need except <?php echo $content_for_layout; ?>
// Any registered user will be allowed to login using the form
// and continue on to your site using the default layout
// But it guarantees no one else can see your default site
$this->layout = "unregistered_user"
}
}
On your App_controller.php you can create this function
function isLoggedIn(){
// You can also use $this->Auth->user directly in your App's beforeFilter()
// But I just like to have functions so I can reuse
if($this->Auth->user()){
$loggedin= TRUE;
}else{
$loggedin= FALSE;
}
return $loggedin;
}
I have something similar of my site but is only used when in maintenance mode. I am still developing my site. The only problem I've seen with this way, which I have not yet have time/need to look at, is that my errors are not sent to the layout I want. Supposed a user types in http://www.mydomain.com/inexistentpage then cake transfers them to my default layout. It might be easy to fix, but I havent got time to do that yet.
NOTE: I quickly did this off the top of my head and because of it, this code is untested. However, if you have any issues please let me know and I will test it and post back.
using $this->requestAction(anotherController/action); in the view might call to another controller->action. you must ensure that the another controller->action has the right permissions. or you'll get redirect loop.
solve it by adding $this->auth->allow('action name'); to the another controller page in the beforeFilter() callback.

Resources