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');
Related
I'm currently trying to build a secure SPA application in Laravel by using :
Laravel 5.6
Laravel Passport
Guzzle client
To make the whole application secure, I created a proxy to prefix all requests to the API and :
User the password grand type of token
Hide the client ID
Hide the client secret
Add automatic scopes based on the role of the user
This is how the Proxy works :
// The proxify endpoint for all API requests
Route::group(['middleware' => ['web']], function ()
{
Route::any('proxify/{url?}', function(Request $request, $url) {
return Proxify::makeRequest($request->method(), $request->all(), $url);
})->where('url', '(.*)');
});
Each time a request is made, it goes through that package I built to create the access token, refreshing it, or deleting it.
To create the access token for the user I'm using a MiddleWare at loggin :
$response = $http->post('http://myproject.local/proxify/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'username' => $request->get('email'),
'password' => $request->get('password'),
]
]);
This is working well, excepting the fact that I'm setting cookies in the Proxify::makeRequest, so I have to create them in the call, return them in the $response, and then at the end of the Middleware, attaching them to the request (Cookie::queue and Cookie::Make are not working in a Guzzle call it seems).
The access token is created and stored in a cookie.
First problem is that in this call, even in the middleware, and especially in that URL http://myproject.local/proxify/oauth/token, I don't have any access to the Auth trait, even if it's specified as a middleware attached to the route, so impossible to fetch information from the authenticated user.
Then the other problem is that when I'm making a call to get a ressource API such as :
$http = new Client();
$response = $http->get('http://myproject.local/proxify/api/continents');
$continents = $response->getBody()->getContents();
return view('dashboard')->with("continents", $continents);
In that case, when I'm calling the URL, the proxy is not able to get the access_token defined in the cookie with the CookieFacade through the HTTP call, neither the Auth object I'm whiling to use. The $_COOKIE variable is not working neither.
What is wrong with my structure so that I don't have any access to the cookie even if it's set and in the browser ? Any other way to get the info ? I tried to get the cookie from the request in the proxy, not working.
Have you tried using the Illuminate or Symfony Request classes and handling the routing via the Laravel instance? My immediate suspicion is Guzzle is the culprit behind no cookies coming through with the requests. Cookie::queue() is a Laravel specific feature so I wouldn't think Guzzle would know anything about them.
Replace Guzzle in one of the routes where the issue occurs. Start with a new Request instance and make the internal api call like:
// create new Illuminate request
$request = Request::create('/api/users', $action, $data, [], [], [
'Accept' => 'application/json',
]);
// let the application instance handle the request
$response = app()->handle($request);
// return the Illuminate response with cookies
return $response->withCookies($myCookies);
I do something similar to this in a couple applications and never had any problems with cookies or accessing server variables. Granted, it's only for authentication, the rest of the api calls are through axios.
Im using Zend Framework 3 and the SessionManager and im trying to build a controller plugin / view helper to display confirm dialogues after validating some Data . The idea was simply to set a Session variable with everything the confirm dialogue needs, reading it by the view, and unsetting it. But even this simple cycle fails. The plugin basically does this when invoked by the controller:
$dataArray = [
'some_data' => 'data'
];
$this->sessionManager->getStorage()->confirmDialog = $dataArray;
in the layout.phtml i call my view Helper which does this:
public function __invoke() {
$data = $this->sessionManager->getStorage()->confirmDialog;
$this->sessionManager->getStorage()->clear('confirmDialog');
return $this->getDialog($data);
}
I do inject the sessionManager to both the plugin and the view helper. When not clearing the variable after receiving its data i get the changed data from the session variable and it gets updated by the Plugin as it should.But when clearing the variable after the first time reading it, its always empty.
Here my global.php setup:
'session_manager' => [
'validators' => [
RemoteAddr::class,
HttpUserAgent::class,
]
],
'session_storage' => [
'type' => SessionArrayStorage::class
]
Because the value is passed by reference, when you clear it, you clear the read information with it also.
As I mentioned in a comment, I suggest using the default falsh messenger plugin, but if you want to create your own plugin, here is a hint from that's source code, which shows you how you can achieve a default clear after read from session.
https://github.com/zendframework/zend-mvc-plugin-flashmessenger/blob/843654a029a19c38e0c3b2e940e59edec75c3e4f/src/FlashMessenger.php#L165
This setting is actually tells the session container to drop that information after '1 hop', ie. in case of a next request.
I need to write a very specific authentication for my web application. There is API on the side which accepts login + password pair and returns the result (and, a token). I don't want to store any login information on the Yii2 side besides a login token i've got from API. And this must be the only way i auth my clients (so i don't use OAuth-like application).
What is the best practive to override "classic" code in Yii2? Just use filters and modify User model?
Example:
First, i recieve a token and save it somewhere for a session:
$token = GatewayAPI::login($user, $password);
Then, every internal request i do will look like this:
$result = GatewayAPI::addPosition($token, $data);
So, i don't have any database to work with, just cache and memory. Almost everything is handled on API side.
My task is to implement login check - if token is recieved from API - then it's considered as a success. And to store that token for use within current session (probably in memcache, it must not be opened to public).
As a matter of fact Yii2 does not require login/password anywhere.
You don't need to modify or extend User model if you mean \yii\web\User.
You need to create your own class implementing IdentityInterface and set this class as userIdentity in your config components->user->identityClass:
[
'components' => [
'user' => [
'class' => 'yii\web\User', // not necessary, this is by default
'identityClass' => 'my\namespace\User'
]
]
]
There are 5 methods in the interface and they are not about login/pass. This class of yours may store in your db everything you want.
For example you may copy any of popular user modules to your project, remove everything related to storing and searching by login/pass from that User model and add your API functionality - and it will work.
UPD.
Your added functionality will look like this:
$token = GatewayAPI::login($user, $password);
$user = \my\namespace\User::findOne(['token' => $token]);
Yii::$app->user->login($user);
I'm realizing that I have a bit of a security hole on my sites, specifically when it's in development mode.
The problem is that you can access the User Guide / API Browser without being logged in. Now the User Guide isn't a big deal, but the API Browser is a bit of a concern as all of my code is visible through it. I'm a bit concerned because some of my development sites are available publicly so others can access then (although they've blocked from being indexed).
I've taken a look at Controller_Userguide and it isn't extended from another controller as other controllers as (such as template). Instead it's the final controller. This being the case doesn't allow me to extend the controller at and something to the before() method.
I thought of excluding the module when users aren't logged in, but I can't because the auth module isn't loaded yet.
I am already only including the user guide (and other modules) when on the development site, so this helps, but I wouldn't call this security.
Any other ideas on how to accomplish this?
Is there any reason to allow logged in users to view the userguide?
I would add something like this to the bootstrap
//Add modules that are only relevant to local development
if(Kohana::$environment == Kohana::DEVELOPMENT)
{
Kohana::modules(array_merge(Kohana::modules(), array(
'codebench' => MODPATH.'codebench', // Benchmarking tool
'userguide' => MODPATH.'userguide', // User guide and API documentation
'unittest' => MODPATH.'unittest', // Unit testing
)));
}
then any public facing sites just change the $environment to something else like STAGING or TESTING
Kohana::$environment = Kohana::TESTING //In the bootstrap file
Alternatively
SetEnv KOHANA_ENV TESTING //to the .htaccess file
OPTION 2 - Load the auth module first
I've just tried this, seems to work for me. In your bootstrap file, load the modules like this:
/**
* Enable modules. Modules are referenced by a relative or absolute path.
*/
Kohana::modules(array(
'auth' => MODPATH.'auth', // Basic authentication
'cache' => MODPATH.'cache', // Caching with multiple backends
'database' => MODPATH.'database', // Database access
'image' => MODPATH.'image', // Image manipulation
'orm' => MODPATH.'orm', // Object Relationship Mapping
));
//Add modules that are only relevant to testing
if(Kohana::$environment == Kohana::DEVELOPMENT and Auth::instance()->logged_in())
{
Kohana::modules(array_merge(Kohana::modules(), array(
'codebench' => MODPATH.'codebench', // Benchmarking tool
'userguide' => MODPATH.'userguide', // User guide and API documentation
'unittest' => MODPATH.'unittest', // Unit testing
)));
}
Option 3 - Isolating the API browser
There is a config option in the userguide config:
// Enable the API browser. TRUE or FALSE
'api_browser' => TRUE,
which you could set to false if the user isn't logged in, similar to the loading of the modules above. The is currently a bug with it that crashes the userguide template because it can't find the route to the API.
IF you want to go to the effort to get this to work (until there is an update), then copy /modules/userguide/views/userguide/template.php to /application/views/userguide/template.php and then replace lines 28 to 30 with this:
<li class="api">
<?php echo __('API Browser') ?>
</li>
and put this in /application/config/userguide.php:
<?php defined('SYSPATH') or die('No direct script access.');
$config = array();
if(Kohana::$environment == Kohana::DEVELOPMENT and Auth::instance()->logged_in())
{
$config['api_browser'] = FALSE;
}
return $config;
I am trying to test a controller action that allows edition of user profiles. Among other things I want to test that every logged user can only edit its own profile and not other's. In case of breaking this restriction the action must redirect to a predefined home page.
With this scenario, I have a fixture that creates a user with ID = 1. So I was thinking on testing the restriction this way:
$data = $this->Users->User->read(null, 1);
$this->Users->Auth->login($data);
$this->testAction('/users/edit/2', array('method' => 'get'));
$url = parse_url($this->headers['Location']);
$this->assertEquals($url['path'], '/homepage');
The test passes this assert. So the next step is to check if executing '/users/edit/1', which has the ID of the logged user, shows the form:
$this->testAction('/users/edit/1', array('method' => 'get', 'return' => 'vars'));
$matcher = array(
'tag' => 'form',
'ancestor' => array('tag' => 'div'),
'descendant' => array('tag' => 'fieldset'),
);
$this->assertTag($matcher, $this->vars['content_for_layout'], 'The edition form was not found');
However this assert fails. After digging around with debug() I've found that $this->Auth->user() returns the whole information but $this->Auth->user('id') returns null. Since I use the latter in a comparison within the action, it evaluates as false and causes the
test to fail.
The curious thing is that it happens when testing but not when executing the action in a browser. So, what's the correct way of testing this action?
Thanks!
The actual correct answer should be using mock objects instead of actually login the user in manually:
$this->controller = $this->generate('Users', array(
'components' => array('Auth' => array('user')) //We mock the Auth Component here
));
$this->controller->Auth->staticExpects($this->once())->method('user') //The method user()
->with('id') //Will be called with first param 'id'
->will($this->returnValue(2)) //And will return something for me
$this->testAction('/users/edit/2', array('method' => 'get'));
Using mocks is the most easy way to test a controller, and also the most flexible one
Update 11 March 2015
You can also mock all method of AuthComponent
$this->controller = $this->generate('Users', array(
'components' => array('Auth') // Mock all Auth methods
));
I like Jose's answer, but when faced with a similar situation I want to use the actual AuthComponent and Session to create a test that would give me confidence.
I am using Controller-based authentication, which means that each controller in my app must provide its own isAuthorized() callback. I want to test MyController::isAuthorized(). It seems too easy to get a test to pass using mocks.
So ,instead of using TestCase::generate() to create a mock controller with mock components, I followed Mark Story's excellent article Testing CakePHP Controllers the hard way and provided my own mock controller that logs in a user with the the real CakePHP AuthComponent.
Here's my work. See the method testIsAuthorized() and the class def for MockAnnouncementsController near the top.
It seems to me that CakePHP testing framework assumes that you want to test controllers only through requestAction(). It was not designed to facilitate direct unit-testing of callback implementations like Controller::isAuthorized() within a controller without mocking the AuthComponent and perhaps other components, and that would give me less confidence in test of that particular method. Nevertheless, I think this is a valid use-case for unit-testing parts of a controller that are not actions (e.g. "index", "view"), but cannot be delegated to a component because they must be called by the core framework. I'm still thinking about how I could abstract it to make it available for any controller.
Instead of:
$this->Auth->user('id')
Try one of these:
$this->Auth->data['User']['id']
$this->Session->read('Auth.User.id')
Set it like so:
$this->Users->Session->write('Auth.User',
array('id' => 1,'and_other_fields_you_need' => 'whatever')
);
Mark Story gives me the answer in a CakePHP ticket. Basically I have to log the user like this:
$data = $this->Users->User->read(null, 1);
$this->Users->Auth->login($data['User']);
instead of
$data = $this->Users->User->read(null, 1);
$this->Users->Auth->login($data);