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
Related
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
I have problems to get cookies to work in cakephp 3.5.x.
in earlier versions I've used the Cookie component but this is now deprecated. Its unclear for me how to use this new middlewarestuff for reading and writing cookies.
The documentation is unclear for me. It shows me how to set up the cookie middleware but not how to handle creating cookies in a controller. Is there anyone who has handled cookies in 3.5.x?
The middleware only replaces the encryption part of the Cookie component (which basically is the only thing it did as of CakePHP 3.0 anyways), if required it automatically encrypts and decrypts the cookies that you've configured.
You do not use the middleware to read or write cookies, that is done via the request and response objects, which is the default since CakePHP 3.
Reading and writing cookies from within a controller action can be as simple as:
$rememberMe = $this->request->getCookie('remember_me');
$this->response = $this->response->withCookie('remember_me', [
'value' => 'yes',
'path' => '/',
'httpOnly' => true,
'secure' => false,
'expire' => strtotime('+1 year')
]);
See also
Cookbook > Controllers > Request & Response Objects > Request > Cookies
Cookbook > Controllers > Request & Response Objects > Response > Setting Cookies
Cookbook > Controllers > Request & Response Objects > Cookie Collections
My case using Cake 3.8, just in case someone is lost as myself:
In your beforeFilter load the component
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
//Load components, like Cookie
$this->loadComponent('Cookie', ['expires' => '30 day']);
}
If cake complains:
Argument 1 passed to App\Controller\PController::beforeFilter() must be an instance of App\Controller\Event, instance of Cake\Event\Event given
Add the following to the top of your class:
use Cake\Event\Event;
And then reading and writing Cookies in your Controller action is breeze:
//Read
$fooVal = $this->Cookie->read('foo');
//Write
$this->Cookie->write('foo', 'bar');
I am developing a small REST app in slim framework. In that, users password is send as encrypted in the request body as xml or json. I want to de-crypt that password in a callable function and update the request body so that in the actual call back function we can validate the password without de-cryptng. I want to do those steps as follows:
$decrypt = function (\Slim\Route $route) use ($app) {
// Decrypt password and update the request body
};
$update = function() use ($app) {
$body = $app->request()->getBody();
$arr = convert($body);
$consumer = new Consumer($arr);
if ($consumer->validate()) {
$consumer->save();
$app->response()->status(201);
} else {
.....
}
}
$app->put('/:consumer_id', $decrypt, $update);
We can modify the body like following way:
$env = $app->environment;
$env['slim.input_original'] = $env['slim.input'];
$env['slim.input'] = 'your modified content here';
Courtsey: ContentTypes middleware
You say you want decrypt the password and update the request body. If you're encrypt the password at client side, i would rather decrypt the password in a server side layer like API service (or something that consume the business layers like a controller in mvc).
I do believe that this decryption process should belong to your application instead of doing it outside before consuming your code. I don't know how you encrypt but if you use server side programming to generate a new hash in those requests, for me that's even a better reason to do it inside the library.
That's how i handle this type of tasks, i try to use only the frameworks for consuming libraries and not handling any logic.
However if you want to do this, you could transform the request body and save it in a new location for services that need to decrypt the password.
I use Middleware for almost every code i need to write specifically to Slim layers. I only passe functions consuming classes that act as API layers and are abstracted from Slim. For your case, use a Middleware to keep this logic in his own place.
class DecriptPasswordRequest extends \Slim\Middleware
{
public function call()
{
$decriptedRoutes = array('login', 'credentials');
$app=$this->app;
$container = $app->container;
$currentRoute = $app->router()->getCurrentRoute();
if ($app->request->getmethod() == 'POST' && in_array($currentRoute, $decriptedRoutes){
$body = $app->request->post();
if (!isset($body['password'])){
throw new Exception('Password missing');
}
$provider = new ClassThatDecryptPassword();
$body['password'] = $provider->decrypt($body['password']);
}
$container['bodydecripted'] = $body;
$this->next->call();
}
}
In my users controller I am trying to put in place the remember me functionality on login. IT seems pretty simple set a cookie look for the cookie when the user visits the login page and if it exists log them in. However, Cake is not saving the cookie, at least not that I can see, and when I revisit the page I am not auto logged in.
To test this I have the following code:
$cookie=$this->read('Auth.User');
if(empty($cookie) and !empty($this->data)){
if($this->Auth->login()) {
if(!empty($this->data['User']['remember_me'])){
$cookie = array('id' => $this->Auth->user('id'),
);
$expires=strtotime($this->appConfigurations['remember_me'],time());
$this->Cookie->write('Auth.User', $cookie, false, $this->appConfigurations['remember_me']);
}
}
}
Now right after I set this cookie I can place a $this->cookie->read('Auth.User'); and get the value of this cookie, however it does not show up in the browsers (Chrome, FireFox) cookie list.
If I use plain PHP cookies, via setcookie() I can see the cookie but of course the Cake Cookie read does not work with those cookies. What should I look for to resolve this issue?
I did find a work around, but I don't like it because it just bypasses the framework. I found out how cake is creating the cookies and for these cookies I use cakes cookie creation algorithm in my code and use setcookie() to do the setting. Just for anyone else that may want or need to use the work around:
$cookieValue=$this->Auth->user('id');
setcookie('CakeCookie[Auth][User]',$cookieValue,$expires,'/');
Now you can use cakes cookie component to read the value. There is more you have to change if your value is an array, read through the cookie.php code to see what you would need to do. Also I left out the encryption that too can be found in the cookie.php and your apps settings. For this issue I do not need array values since I am only store the users ID. and I did put in place encryption unlike above.
I would still like to know why the component is not working though.
The following login action works well for me :
function login() {
$cookie = $this->Cookie->read('Auth.User');
debug($cookie); // Just a test
if ($this->Auth->user('id')) {
if(!empty($this->data)) {
$cookie = array(
'username' => $this->data['User']['username'],
'password' => $this->data['User']['password']
);
$this->Cookie->write('Auth.User', $cookie, false, '+2 weeks');
}
$this->redirect('/');
}
elseif (!empty($cookie)) {
if ($this->Auth->login($cookie)) {
$this->redirect('/');
}
}
}
Does it work on your side ?
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)