How can I use cookies for authentication in CakePHP? - cakephp

I am trying to use a cookie that is set by other page in my domain to authenticate the user.
Say I have needpassword.example.com written using cakephp,
and the cookie is generated by auth.example.com (using a Perl CGI program).
To login in to needpassword.example.com, I need to redirect to auth.example.com to set the cookie, and then use CakePHP to parse the cookie.
How do I parse this cookie? And how do I modify the Auth component to do these?
And how can I override the Auth class to instead go to the auth.example.com to authenticate, and not using the User model? By overriding the identify method in Auth.php?
Many thanks.

Since your needs sound outwith AuthComponent's originally intended design you have two options.
Firstly, if it really doesn't fit your needs, you could create and maintain your very own AuthComponent. Do this by copying /cake/libs/controller/components/auth.php to /app/controller/components/auth.php.
This would allow you to rewrite the component completely, but the downside is you will no longer receive updates to AuthComponent when you upgrade cake.
Secondly, you can extend just about anything in CakePHP using the following pattern:
// save as: /app/controllers/components/app_auth.php
App::import('Component', 'Auth');
class AppAuthComponent extends AuthComponent {
function identify($user = null, $conditions = null) {
// do stuff
return parent::indentify($user, $conditions);
}
}
.. and replace all instances of AuthComponent in your controllers with your AppAuthComponent.
You only need to define the methods you wish to replace.
You can run methods from the original AuthComponent (even ones you have redefined) at any point during your methods using parent::...
The method arguments should remain in the same order as the original API for consistency.
If you wish to add more method arguments, put them after the API ones, eg:
function identify($user = null, $conditions = null, $custom = array()) { ... }
This approach allows you to make application-specific customisation while still using the latest methods defined in the core where necessary.

Presuming I understand your question... As long as auth.example.com sets the cookie with the domain ".example.com" the users browser will send it along with the request to needpassword.example.com and you will be able to access it in your PHP script with the following:
$auth = $_COOKIE['auth'];
You can then make changes to the cookie with the following:
setcookie( "auth", "value", time() + 300, "/", ".example.com" );
(Note: time() + 300 sets the cookies expiry date to 5 minutes in the future, you may want to change this)

Related

How to use a CookieCollection in multiple functions

I'm setting up a web page using cookies to determine if the user already logged in, using a cookie containing his id. Problem is : The cookie is either not written or the cookie collection is not updated.
I've tried reading the documentation, but it does not define the usage of CookieCollection.
Here's the function where i write my cookie :
function displayData(){
$id = $this->getRequest()->getSession()->read('id');
$cookies = CookieCollection::createFromServerRequest($this->getRequest());
if(!$cookies->has('id')){
$cookie = (new Cookie('id'))
->withValue($id)
->withExpiry(new DateTime('+999 year'))
->withPath('/')
->withDomain('break-first.eu')
->withSecure(true)
->withHttpOnly(true);
$cookies = $cookies->add($cookie);
}
// Other stuff
}
And where I try reading it :
function index(){
$cookies = $this->getRequest()->getCookieCollection();
dd($cookies);
}
I expect having a cookie named "id", but I don't have it. Only CAKEPHP and pll_language are showing up.
First things first, CakePHP provides authentication functionality with cookie authentication, you may want to have a look at that instead of driving a custom solution.
Cookbook > Plugins > Authentication
That being said, what you're doing there will create a cookie collection object, which however is just that, a lone object somewhere in space, it won't affect the state of your application, in order for that to happen you have to actually modify the response object.
However what you're trying to do there doesn't require cookie collections in the first place, you can simply read and write cookies directly via the methods provided by the request and response objects, like:
// will be `null` in case the cookie doesn't exist
$cookie = $this->getRequest()->getCookie('id');
// responses are immutable, they need to be reassinged
this->setResponse(
$this->getResponse()->withCookie(
(new Cookie('id'))
->withValue($id)
->withExpiry(new DateTime('+999 year'))
->withPath('/')
->withDomain('break-first.eu')
->withSecure(true)
->withHttpOnly(true)
)
);
And if you where to use a cookie collection for whatever reason, then you'd use withCookieCollection() to pass it into the response:
$this->setResponse($this->getResponse()->withCookieCollection($cookies));
If you run into strict typing errors, you could for example create a custom reponse class with an overridden Response::convertCookieToArray() method and cast the string to an integer there (make sure that PHP_INT_MAX covers your target date timestamp, 32-Bit incompatibility is why the fix that landed in CakePHP 4.x, probably won't come to 3.x), something like:
src/Http/Response.php
namespace App\Http;
use Cake\Http\Cookie\CookieInterface;
use Cake\Http\Response as CakeResponse;
class Response extends CakeResponse
{
protected function convertCookieToArray(CookieInterface $cookie)
{
$data = parent::convertCookieToArray($cookie);
$data['expire'] = (int)$data['expire'];
return $data;
}
}
You can pass that into the app in your webroot/index.php file, as the second argument of the $server->run() call:
// ...
$server->emit($server->run(null, new \App\Http\Response()));
See also
Cookbook > Request & Response Objects > Request > Cookies
Cookbook > Request & Response Objects > Response > Setting Cookies

How can CakePHP Authorization plugin authorize access to indexes?

I'm converting my app to CakePHP 3.6, and working now on using the new Authorization plugin. I'm not sure how to check authorization for things like indexes or other reports, where there is no "resource" to pass to the can() or authorize() functions.
For now, I've built a ControllerResolver, loosely copied from the ORMResolver, which accepts controller objects and finds policies based on the singularized controller name, so that they're named the same as the Entity policies I'm building. (That is, my UserPolicy can have canIndex and canEdit functions, the former found via the controller and the latter via the entity.)
This works fine in controller actions where I can call $this->Authorize->authorize($this);, but it doesn't work in views, where I'd like to be able to do things like:
if ($this->Identity->can('index', *something*)) {
echo $this->Html->link('List', ['action' => 'index']);
}
so as to only show links to people who are allowed to run those actions.
Anyone know if there's a reason why the system implicitly requires that the "resource" passed into authorization functions be an object? (For example, the plugin component calls get_class($resource) in the case of a failed authorization, without first checking that the provided resource is in fact an object.) Allowing a string (e.g. \App\Controller\UsersController::class) would make my life easy. Very happy to put together a PR for this if it's just an oversight.
But authorizing indexes seems like a pretty obvious function, so I wonder if I've missed something. Maybe I'm supposed to pass the table object, and split the authorization between an entity policy and a table policy? But using table objects in views just for this purpose seems like a violation of separation of concerns. Maybe uses of the plugin to date have been things where indexes are always public?
to do this you can use the authorizeModel, as stated in the documentation https://github.com/cakephp/authorization/blob/master/docs/Component.md#automatic-authorization-checks. Basically is adding the auhtorizeModel parameters when you load the component at AppController.php
$this->loadComponent('Authorization.Authorization', [
'skipAuthorization' => ['login','token'],
'authorizeModel' => ['index','add'],
]);
When you configure an action to be authorized by model the authorization service uses the TablePolicy, so if you want to authorize the index action for Books you need to create the BooksTablePolicy and implement the method
<?php
namespace App\Policy;
use App\Model\Table\BooksTable;
use Authorization\IdentityInterface;
/**
* Books policy
*/
class BooksTablePolicy
{
public function scopeIndex($user, $query)
{
return $query->where(['Books.user_id' => $user->id]);
}
public function canIndex(IdentityInterface $identity)
{
// here you can resolve true or false depending of the identity required characteristics
$identity['can_index']=true;
return $identity['can_index'];
}
}
This will be validated before the request reaches your controller so you do not need to authorize anything there. Nevertheless if you want to apply an scope policy as you can see in this example:
public function index()
{
$user = $this->request->getAttribute('identity');
$query = $user->applyScope('index', $this->Books->find()->contain('Users'));
$this->set('books', $this->paginate($query));
}

CakePHP 3.5 Writing & Encrypting Cookie

Background: I have just upgraded to CakePHP 3.5.17.
I had a code that write cookie. However, it seems that I am missing a few steps to encrypt it. Can somebody shed some lights where are the missing steps? At the moment, the web browser is getting the value of the cookie but it is not encrypted. Note I have also set the cookieKey on my app.php
I've also included this steps in the link provided below
https://book.cakephp.org/3.0/en/development/application.html#adding-http-stack
//In src/Controller/UsersController.php
use Cake\I18n\Time;
use Cake\Http\Cookie\Cookie;
use Cake\Http\Cookie\CookieCollection;
use Cake\Core\Configure;
use App\Application;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
use Cake\Http\Middleware\EncryptedCookieMiddleware;
public function writecookie() {
$cookie = new Cookie(
'goodday', // name
'YES', // value
(Time::now())->modify('+1 year'), // expiration time, if applicable
'/', // path, if applicable
'', // domain, if applicable
false, // secure only?
true // http only ?
);
$middlewareQueue = new MiddlewareQueue();
$cookiesEncrypted = new EncryptedCookieMiddleware(
['goodday'],
Configure::read('Security.cookieKey')
);
$cookiesEncrypted = $middlewareQueue->add($cookiesEncrypted);
$this->response = $this->response->withCookie($cookie); //value is still YES in the web browser cookie storage
}
After further debugging, I noticed that in class EncryptedCookieMiddleware. It is stating that Cookies in request data will be decrypted, while cookies in response headers will be encrypted automatically. If the response is a Cake\Http\Response, the cookie data set with withCookie() and `cookie()`` will also be encrypted. But for me it doesn't automatically encrypt?
You may want to make yourself more familiar with how middlewares work, you're not supposed to use them in your controller, they're supposed to be "wrapped around" your application and interact with the requests that are sent to the app, and the responses that the app sends back.
You register them in your applications Application::middleware() method, in the Server.buildMiddleware event, or when connecting routes.
// src/Application.php
// ...
use Cake\Http\Middleware\EncryptedCookieMiddleware;
class Application extends BaseApplication
{
public function middleware($middlewareQueue)
{
// ...
$middlewareQueue->add(new EncryptedCookieMiddleware(/* ... */));
return $middlewareQueue;
}
}
See also
Cookbook > Middleware
Cookbook > Middleware > Using Middleware
Cookbook > Routing > Connecting Scoped Middleware

Cakephp 3 - CRUD plugin - Use id from auth component

Currently, I'm using the CRUD v4 plugin for Cakephp 3. For the edit function in my user controller it is important that only a user itself can alter his or her credentials. I want to make this possible by inserting the user id from the authentication component. The following controller method:
public function edit($id = null){
$this->Crud->on('beforeSave', function(\Cake\Event\Event $event) {
$event->subject()->entity->id = $this->Auth->user('id');
});
return $this->Crud->execute();
}
How can I make sure I don't need to give the id through the url? The standard implementation requires the url give like this: http://domain.com/api/users/edit/1.json through PUT request. What I want to do is that a user can just fill in http://domain.com/api/users/edit.json and send a JSON body with it.
I already tried several things under which:
$id = null when the parameter is given, like in the example above. Without giving any id in the url this will throw a 404 error which is caused by the _notFound method in the FindMethodTrait.php
Use beforeFind instead of beforeSave. This doesn't work either since this isn't the appropriate method for the edit function.
Give just a random id which doesn't exist in the database. This will through a 404 error. I think this is the most significant sign (combined with point 1) that there is something wrong. Since I try to overwrite this value, the CRUD plugin doesn't allow me to do that in a way that my inserting value is just totally ignored (overwriting the $event->subject()->entity->id).
Try to access the method with PUT through http://domain.com/api/users.json. This will try to route the action to the index method.
Just a few checks: the controllerTrait is used in my AppController and the crud edit function is not disabled.
Does anyone know what I'm doing wrong here? Is this a bug?
I personally would use the controller authorize in the Auth component to prevent anyone from updating someone else's information. That way you do not have to change up the crud code. Something like this...
Add this line to config of the Auth component (which is probably in your AppController):
'authorize' => ['Controller']
Then, inside the app controller create a function called isAuthorized:
public function isAuthorized($user) {
return true;
}
Then, inside your UsersController you can override the isAuthorized function:
public function isAuthorized($user) {
// The owner of an article can edit and delete it
if (in_array($this->request->action, ['edit'])) {
$userId = (int)$this->request->params['pass'][0];
if ($user['id'] !== $userId) {
return false;
}
}
return parent::isAuthorized($user);
}

cakephp Session->write problem in linux

I have problem with cakephp's Session->write method.
If I set a value like $_SESSION['..'] i'm able to read it back. But if I use the write method it's not working.
My problem is same as here: http://www.nabble.com/Session-problem-td16684956.html
The same code was working in windows but it's not working after I moved to linux.
Any permission problem would be the reason? (but i have given rw permission fully for the cake app directory).
code sample: in the link: http://www.nabble.com/Session-problem-td16684956.html
Configure::write('Session.save', 'php');
Configure::write('Session.cookie', 'CAKEPHP');
Configure::write('Session.start', true);
Configure::write('Session.checkAgent', false);
Configure::write('Security.level', 'medium');
cake version: 1.2.3.8166
Some steps to ensure it's not you:
clear the cache in your /app/tmp
check and recheck that your /app/tmp is world-writable recursively (that means drwxrwxrwx for all folders inside)
use Firebug to check your session cookie, maybe something has gone wrong with it
Last but not least, try to move your session persistence to your database (see: Session.save), just to test things out that way, you never know what you'll find.
Hopefully you'll find something if you try all these.
You should also try to use Cache::read and Cache::write
if (($session = Cache::read('session')) === false)
{
$session = 'some values';
Cache::write('session', $session);
}
Firstly, it will try to initialize Cache::read. If it returns false, Cache::write
will take part to store the values in sessions.
Prabu,
While I suspect the Configure::write() call will sometimes correctly set the session information (at least it looks like it might work), the Cake convention (aka the CakeWay) is to use the Session helper. I believe it is included by default in all Cake controllers; if not, you can always declare your controller as such:
class UsersController extends AppController {
...
var $helpers = array( 'Session', ... )
...
}
Then, when you want to write info to the session, just call:
$this->Session->write( 'checkAgent', false );
To read back values, use:
$this->Session->read( 'checkAgent');
For more information on the Session helper, check out the CakeBook # http://book.cakephp.org/view/484/Session

Resources