I have an intranet app running on IIS, using CakePHP 3. From IIS I am able to access the server var $_SERVER['AUTH_USER'] and I want to use this variable to authenticate users.
I have created a users table in my database with a username field that I want to match to AUTH_USER. I have created a custom Auth component like so:
namespace App\Auth;
use Cake\Auth\BaseAuthenticate;
use Cake\Network\Request;
use Cake\Network\Response;
use Cake\ORM\TableRegistry;
class AuthuserAuthenticate extends BaseAuthenticate
{
public function authenticate(Request $request, Response $response) {
$username = str_replace('DOMAIN\\', '', $_SERVER['AUTH_USER']);
$users = TableRegistry::get('Users');
$user = $users->find()->where(['username' => $username])->first();
if ($user) {
return $user;
} else {
$user = $this->Users->newEntity();
$user->username = $username;
if ($this->Users->save($user)) {
return $user;
} else {
return false;
}
}
}
And in the AppController initialize() I have tried to load Auth with the custom component.
$this->loadComponent('Auth', [
'authenticate' => [
'Basic' => [
'fields' => ['username' => 'username'],
'userModel' => 'Users'
],
],
'loginAction' => [
'controller' => 'Pages',
'action' => 'display'
],
'storage' => 'Memory',
'unauthorizedRedirect' => false
]);
$this->Auth->config('authenticate', 'Authuser');
At this point I just get redirected no matter what page I try to go on, I'm not really sure if it's failing to authenticate or something else is the problem.
I have tried adding this to AppController as a test:
public function isAuthorized($user)
{
return true;
}
But I am unable to access any pages with this code in place. Can anyone let me know what I'm doing wrong?
Thanks,
Kez
Your auth component is not implementing the authorize method.
public function authorize($user, Request $request) {
// return true if authorized
// return false if not authorized
}
Secondly, isAuthorized is called when using the ControllerAuthorize component. If you want to use controller authentication, you should use ControllerAuthorize insted.
$this->loadComponent('Auth', [
'authenticate' => 'Controller'
]);
Also: You are configuring the BasicAuthenticate component, then immediately overwriting the config.
Related
I have a cakephp website. After login the user should be redirected to a new page, this is working on local. But on dev site it is giving issues .
When the user clicks on submit the URL of the next page gets appended to the current URL and the page is not redirecting.
This is the login function from Userscontroller
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect(['controller'=>'Recipes','action' => 'index']);
//return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('Your username or password is incorrect.');
}
}
This is the initialize from AppController
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler', [
'enableBeforeRedirect' => false,
]);
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'storage' => 'Session',
// If unauthorized, return them to page they were just on
'unauthorizedRedirect' => $this->referer()
]);
// $this->Auth->setConfig('authorize', ['Controller']);
// Allow the display action so our PagesController
// continues to work. Also enable the read only actions.
/* $this->Auth->allow(['display', 'view', 'index','register','forgotPassword']);*/
/*
* Enable the following component for recommended CakePHP security settings.
* see https://book.cakephp.org/3.0/en/controllers/components/security.html
*/
//$this->loadComponent('Security');
}
I have also attached a screenshot of whats happening
The issue is solved. it seems the issues occurred because of cache as it is working on other computer . i cleared the cache it is working now.
I am trying to deny some actions for non-admin users in my controllers. So in the controllers I am using this code:
public $components = array('Auth');
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
if($this->Auth->user('role_id')==1 or $this->Auth->user('role_id')==2){ //role: 1 admin, 2 project manager
$this->set('is_admin', true);
}
else
{
$this->Auth->deny(['index','delete']);
$this->set('is_admin', false);
}
$this->set('my_id', $this->Auth->user('id'));
}
So now anytime a user that is not admin or project manager tries to perform index or delete actions, is redirected to the "Method Not Allowed" error page. But I would like to return to the previous page with a message: "You are not authorized to perform this action".
I tried to set 'unauthorizedRedirect' => $this->referer() in the AppController:
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'unauthorizedRedirect' => $this->referer()
]);
But didn't work. The only way I managed to achieve that is by using this code in the beforeFilter function of the controller:
if(!($this->Auth->user('role_id')==1 && !$this->Auth->user('role_id')==2 && ($this->request->action === 'index' || $this->request->action === 'delete')){
$this->Flash->error(__('You are not authorized to perform this action'));
return $this->redirect(['controller' => 'Users', 'action' => 'index']);
}
But it doesn't seem the proper way to do this in all the controllers that I want to deny some actions. Is there another way?
Cakephp provides isAuthorized function for the same. You can take advantage of it.
Just define isAuthorized in your App controller or in separate controllers(if you want to put separate conditions for each controllers.)
public function isAuthorized($user)
{
$roleArray = [1, 2]; // your role ids array
if ( !in_array($user['role_id'], $roleArray) && in_array($this->request->getParam('action'), ['index', 'delete'])) { // put your conditions here
return false;
}
return true;
}
Cakephp -> Authentication and Authorization -> Authorization (who’s allowed to access what)
I've turned the backend into an API. And the API controllers have HttpBasicAuth type authentication.
The problem is that even after authentication in the frontend, whenever a request is made to the API, the authentication window appears.
How can I do so that when the user authenticates in the frontend, is not requested again the username and password of access when a request is made to the API?
Example a controller in API:
class CategoryController extends ActiveController
{
public $modelClass = 'api\models\Category';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => CompositeAuth::className(),
'authMethods' => [
[
'class' => HttpBasicAuth::className(),
'auth' => function($username, $password) {
$out = null;
$user = \common\models\User::findByUsername($username);
if ($user != null) {
if ($user->validatePassword($password)) $out = $user;
}
return $out;
}
],
],
];
return $behaviors;
}
}
It is called sharing sessions. It also depends on if your tier apps (frontend and api) are both in the same domain. If it is, configure your frontend and api settings (<app>/frontend/config/main.php and <app>/api/config/main.php) as follow:
'components' => [
...
'request' => [
'csrfParam' => '_csrf-shared',
],
...
'user' => [
'identityClass' => 'common\models\User',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-shared', 'httpOnly' => true],
],
...
'session' => [
'name' => 'advanced-shared',
],
...
It means you save cookies and session with the same name, so that when you login in frontend, and go to backend/api, the backend side fetches same cookies, therefore you'll be detected as authenticated user.
Here one important note, in order to enableAutoLogin work for both tiers, you should set same cookieValidationKey for both main-local.php settings. You can just set them manually, or edit init.php file to generate one cookieValidationKey for all tiers. (Just make sure you know what you're doing).
By the way, I think it's not a good idea to make simultaneous authentication between frontend and api. If it's frontend and backend then it's still bearable, but api interaction is different compared to frontend.
I suggest to use headers like Authorization: Bearer <token>.. You can get more information about it here Yii2 Rest Authentication
Update
I assume this is what you need.. Create a class, i.e. ApiAuth in common/components folder and paste the following code:
<?php
namespace common\components;
use yii\filters\auth\HttpBasicAuth;
class ApiAuth extends HttpBasicAuth
{
/**
* #inheritdoc
*/
public function authenticate($user, $request, $response)
{
if ($user->identity) {
return $user->identity;
}
return parent::authenticate($user, $request, $response);
}
}
This class extends from yii\filters\auth\HttpBasicAuth. Before calling a browser prompt it checks whether user->dentity is populated. If so, no promt is required.
In your Controller behavior replace HttpBasicAuth with ApiAuth class:
use common\components\ApiAuth;
...
'authMethods' => [
[
'class' => ApiAuth::className(),
'auth' => function($username, $password) {
...
As the user is already authenticated, then I just set the "AccessControl" for connected users. If they are not connected they will receive code 403 instead of 401.
This solves the problem:
class CategoryController extends ActiveController
{
public $modelClass = 'api\models\Category';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['access'] = [
'class' => \yii\filters\AccessControl::className(),
'rules' => [
[
'allow' => true,
'roles' => ['#'],
],
],
];
return $behaviors;
}
}
What I need to achieve is to have the possiblility for administrator to remove session of given user to force relogin. This can come in handy when for example user permissions changes. How to bind session and user together so other user can access that data?
I am using database session store so removing record from the database would result in force relogin. Also authentication is based on Auth component.
Here is some of my related config:
$this->loadComponent('Auth', [
'loginAction' => [
'controller' => 'Auth',
'action' => 'login'
],
'loginRedirect' => "/home",
'logoutRedirect' => [
'controller' => 'Auth',
'action' => 'login'
],
'authError' => "You have no permissions to access resource ${url}. Contact system administrator.",
'storage' => 'Session',
'authenticate' => [
'Form' => [
'userModel' => 'User',
'finder' => 'user',
'fields' => ['username' => 'name', 'password' => 'password']
]
],
'authorize' => ["Controller"]
]);
And session storage:
'Session' => [
'defaults' => 'database',
]
Below I have marked where would i place database update code. Unfortunetly after login execution, session gets "revalidated" so the ID changes. All in all, changes made in login action are not visible after redirect.
Login action:
public function login()
{
$form = new LoginForm();
if ($this->request->is('post')) {
if ($form->validate($this->request->data)) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
// here would be good place to update the database
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Flash->error("Invalid security credentials provided");
}
} else {
$this->Flash->error("Invalid login form");
}
}
$this->set('loginForm', $form);
}
#ndm Thanks for valuable comments and pinpoints.
Thank to #ndm's comments I have found (most probably) viable solution. I had to inject some code into session data save process. To do that, I have used custom SessionHandler and PHP's Cloasure in role of callback.
class UserSession extends DatabaseSession
{
public $callback;
public function write($id, $data)
{
$result = parent::write($id, $data);
if ($this->callback) {
call_user_func($this->callback,$id);
}
return $result;
}
}
and the login action
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
/** #var UserTable $model */
$model = $this->loadModel("User");
$handler = $this->Session->engine();
/** #var UserSession $handler */
$handler->callback = function ($id) use ($model, $user) {
$model->updateSession($id, $user['id']);
};
return $this->redirect($this->Auth->redirectUrl());
}
This way, the session row in the DB is updated AFTER session data is flushed into the DB.
Now I can query all sessions for given user, and delete them if needed forcing relogin for that user. Callback is called only once upon user login, whitch was as well my goal in matter of optimalization and avoidance of UPDATE query db during every http request (because that would be the solution as well, but I wanted to avoid that)
I guess this is a silly question but I'm having trouble logging in to the restricted sections of a site I'm building in Cake.
For starting I see that the password string in $this->request->data['Usuario']['clave'] is not the same as the hashed string using SimplePasswordHasher in the beforeSave function at the model. I should also say that the model is not the default Users model, because I'm writing the application for spanish language and I didn't want to use the default model, so my configuration of the component is:
class AppController extends Controller {
/*function beforeFilter() {
date_default_timezone_set('America/Mexico_City');
}*/
public $components = array(
'Session',
'Auth' => array(
'Form'=>array(
'userModel' => 'Usuario',
'unauthorizedRedirect' => false,
'loginRedirect' => array(
'controller' => 'ComplejosResidenciales',
'action' => 'index'
),
'logOutRedirect' => array(
'controller' => 'Usuarios',
'action' => 'index'
),
'fields' => array(
'username' => 'usuario',
'password' => 'clave'
),
'authorize' => 'Controller'
)
)
);
}
So I decided not to hash the password field, but still to no avail.
I wish anyone could lend me a hand on this because I'm newbie to CakePHP and don't know how to fix it.
I figure it must be something with the Auth->login() method because I'm not following conventions here, but I don't know how to configure the said method. Currently is like follows:
public function login() {
if($this->request->is('post')) {
if($this->Auth->login()) {
return $this->Auth->redirectUrl($this->Auth->redirectUrl());
}
else {
$this->Session->setFlash(__('Las credenciales proporcionadas no son correctas'), 'default', array(), 'auth');
}
As rrd pointed, my $components array was wrong, so I changed it to:
public $components = array(
'Session',
'Auth'=>array('loginRedirect'=>array('controller'=>'ComplejosResidenciales', 'action'=>'index'), 'logOutRedirect'=>array('controller'=>'Usuarios', 'action'=>'index'), 'loginAction'=>array('controller'=>'Usuarios', 'action'=>'login'), 'authenticate'=>array('Form'=>array('userModel'=>'Usuario', 'fields'=>array('username'=>'usuario', 'password'=>'clave')))));
Which is better, according to cakephp.org
Do not put other Auth configuration keys (like authError, loginAction etc) within the authenticate or Form element. They should be at the same level as the authenticate key.
But it isn't working.
Been struggling with it and I can't get the hang of it, I wish someone would point out what I'm doing wrong. In my AppController I have declared the component and the beforeFilter function like this:
public $components = array('Auth'=>array('loginRedirect'=>array('controller'=>'ComplejosResidenciales', 'action'=>'index'),
'logoutRedirect'=>array('controller'=>'Usuarios', 'action'=>'login'),
), 'Session');
public function beforeFilter(){
$this->Auth->authenticate = array(
AuthComponent::ALL => array('userModel' => 'Usuario', "fields" => array("username" => "usuario", "password" => "clave"), 'Form'));
}
And then I have the login function which goes (obviously I guess) in the UsuariosController, like this:
public function login() {
if($this->request->is('post')) {
if($this->Auth->login()) {
return $this->Auth->redirectUrl($this->Auth->loginRedirect);
}
else {
$this->Session->setFlash(__('Las credenciales proporcionadas no son correctas'), 'default', array(), 'auth');
}
}
}
But I just keep seeing the message "Las credenciales proporcionadas no son correctas". I don't know if I'm calling the method of Auth component correctly in the part $this->Auth->login() because apparently I have no result when calling it like that, without arguments, but I tried calling it with the argument $this->request->data and as a result it didn't mattered what I wrote in the username and password fields, anything would pass, which is bad, of course.
Now I see why coding $this->Auth->login($this->request->data) resulted in giving unrestricted access:
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.
According to cake's manual. I can't seem to read correctly any document about this. Anyway, I beg someone would help me because I'm in a hurry here. After reading some other documents I guess that Auth component should handle everything correctly, as long as I provide the right configuration, so I've ended up doing a beforeFilter() call in the AppController, like this:
var $components = array('Auth', 'Session');
public function beforeFilter() {
$this->Auth->loginAction = array('controller'=>'Usuarios', 'action'=>'login');
$this->Auth->redirectLogin = array('controller'=>'ComplejosResidenciales', 'action'=>'add');
$this->Auth->authenticate = array('Form'=>array('userModel'=>'Usuario', 'fields'=>array('username'=>'usuario', 'password'=>'clave')));
}
Then, in my "UsuariosController" I do:
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('index', 'view', 'add', 'edit', 'delete');
}
And I have my login and logout functions, very simple, but it's not working, and it does not redirect me upon logging in nor does it let me access any other controller, it seems to do nothing. Please help!
/**
* login and logout functions
*
*/
public function login() {
}
public function logout() {
$this->redirect($this->Auth->logout());
}
You should change more things.
$components should be something like this:
public $components = array(
'Session',
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array('username'=>'usuario', 'password'=>'clave')
)
)
)
);
I am not sure about userModel, check the manual.
Than you should implement isAuthorized
function isAuthorized($user){
if(in_array($this->action, array('view', 'edit')))
return true;
return false;
}
beforeFilter is not necessary for your case.
Your login method is something like this.
public function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirectUrl());
}
else {
$this->Session->setFlash(__('Username or password is incorrect'), 'default', array(), 'auth');
}
}
}
Again, I recommend you to read the manual or the book mentioned above. It has a free sample, I think You will get the main idea by that.