Reputation: 1096
Alright, I have been following this example of sharing a session between php and nodejs using redis : https://gist.github.com/mscdex/9507b0d8df42e0aec825
I got that working beautifully, but now I am trying to see how I would go about it with CakePhp. I have a few questions about how I would go about it:
Should I just create a new session handler and inside the constructor run the session configurations that I need to do before the session start??
Should I just create a new Session.php class extended from the one provided by CakePhp? If I do this, how would I get the application to use it?
Code linked above:
var express = require('express'),
app = express(),
cookieParser = require('cookie-parser'),
session = require('express-session'),
RedisStore = require('connect-redis')(session);
app.use(express.static(__dirname + '/public'));
app.use(function(req, res, next) {
if (~req.url.indexOf('favicon'))
return res.send(404);
next();
});
app.use(cookieParser());
app.use(session({
store: new RedisStore({
// this is the default prefix used by redis-session-php
prefix: 'session:php:'
}),
// use the default PHP session cookie name
name: 'PHPSESSID',
secret: 'node.js rules',
resave: false,
saveUninitialized: false
}));
app.use(function(req, res, next) {
req.session.nodejs = 'Hello from node.js!';
res.send('<pre>' + JSON.stringify(req.session, null, ' ') + '</pre>');
});
app.listen(8080);
<?php
// this must match the express-session `secret` in your Express app
define('EXPRESS_SECRET', 'node.js rules');
// this id mutator function helps ensure we look up
// the session using the right id
define('REDIS_SESSION_ID_MUTATOR', 'express_mutator');
function express_mutator($id) {
if (substr($id, 0, 2) === "s:")
$id = substr($id, 2);
$dot_pos = strpos($id, ".");
if ($dot_pos !== false) {
$hmac_in = substr($id, $dot_pos + 1);
$id = substr($id, 0, $dot_pos);
}
return $id;
}
// check for existing express-session cookie ...
$sess_name = session_name();
if (isset($_COOKIE[$sess_name])) {
// here we have to manipulate the cookie data in order for
// the lookup in redis to work correctly
// since express-session forces signed cookies now, we have
// to deal with that here ...
if (substr($_COOKIE[$sess_name], 0, 2) === "s:")
$_COOKIE[$sess_name] = substr($_COOKIE[$sess_name], 2);
$dot_pos = strpos($_COOKIE[$sess_name], ".");
if ($dot_pos !== false) {
$hmac_in = substr($_COOKIE[$sess_name], $dot_pos + 1);
$_COOKIE[$sess_name] = substr($_COOKIE[$sess_name], 0, $dot_pos);
// https://github.com/tj/node-cookie-signature/blob/0aa4ec2fffa29753efe7661ef9fe7f8e5f0f4843/index.js#L20-L23
$hmac_calc = str_replace("=", "", base64_encode(hash_hmac('sha256', $_COOKIE[$sess_name], EXPRESS_SECRET, true)));
if ($hmac_calc !== $hmac_in) {
// the cookie data has been tampered with, you can decide
// how you want to handle this. for this example we will
// just ignore the cookie and generate a new session ...
unset($_COOKIE[$sess_name]);
}
}
} else {
// let PHP generate us a new id
session_regenerate_id();
$sess_id = session_id();
$hmac = str_replace("=", "", base64_encode(hash_hmac('sha256', $sess_id, EXPRESS_SECRET, true)));
// format it according to the express-session signed cookie format
session_id("s:$sess_id.$hmac");
}
// https://github.com/TheDeveloper/redis-session-php
require('redis-session-php/redis-session.php');
RedisSession::start();
$_SESSION["php"] = "Hello from PHP";
if (!isset($_SESSION["cookie"]))
$_SESSION["cookie"] = array();
echo "<pre>";
echo json_encode($_COOKIE, JSON_PRETTY_PRINT);
echo json_encode($_SESSION, JSON_PRETTY_PRINT);
echo "</pre>";
?>
Upvotes: 0
Views: 529
Reputation: 60483
I'm not very familiar with Redis or Node, but from looking at the code of the RedisSession
class (https://github.com/TheDeveloper/redis-session-php), I'd say you'll have to go with a custom session handler. Whether the session handler should fumble with cookies is highly debatable, I'd probably put that somewhere else in the bootstrapping process, maybe as a dispatcher filter.
However if you need the session ID to be in a specific format, then you'll also have to make use of a custom session class, at least unless you want to/can make use of the undocumented session id genereation handler stuff that was introduced with PHP 5.5.1.
Creating an extended session class that handles this is fairly easy, just overwrite the start()
and renew()
method and do whatever you need to do with the ID.
Injecting the new session class into the application is pretty easy, as throughout the framework the session is being retrieved from the request (\Cake\Network\Request::session()
). However getting your custom class into the request is a little ugly, as there is no clean way to hook this into the process of creating a request from globals. In any case you'll have to modify your front controller (webroot/index.php
) so that the proper(ly configured) request class is being passed to the dispatcher.
You can either
create a custom request class with for example an overwritten Request::createFromGlobals()
method where you'll instantiate your custom session class and pass it to the config
instantiate a new request class manually where you could pass the session object to use using the session
config key (this will require you to figure the base
and webroot
options yourself)
or overwrite the already assigned/constructed session class with your custom one using the Request::session()
method.
See also
In this example I'll go with a custom request class, simply to avoid the additional session class instantiation.
src/Network/MyCustomSession.php
namespace App\Network;
use Cake\Network\Session;
class MyCustomSession extends Session
{
public function start()
{
parent::start();
$this->_processSessionId();
}
public function renew()
{
parent::renew();
$this->_processSessionId();
}
protected function _processSessionId()
{
$id = $this->id();
// To make this less handler specific, you could for example
// use a configurable callback instead, or maybe even an event,
// in the end this is just example code.
if($id && substr($id, 0, 2) !== 's:') {
$hmac = str_replace(
"=", "", base64_encode(hash_hmac('sha256', $id, \EXPRESS_SECRET, true))
);
$this->id("s:$id.$hmac");
}
}
}
src/Network/MyCustomRequest.php
namespace App\Network;
use Cake\Network\Request;
class MyCustomRequest extends Request
{
public static function createFromGlobals()
{
list($base, $webroot) = static::_base();
$sessionConfig = (array)Configure::read('Session') + [
'defaults' => 'php',
'cookiePath' => $webroot
];
$config = [
'query' => $_GET,
'post' => $_POST,
'files' => $_FILES,
'cookies' => $_COOKIE,
'environment' => $_SERVER + $_ENV,
'base' => $base,
'webroot' => $webroot,
// here comes the custom session
'session' => MyCustomSession::create($sessionConfig)
];
$config['url'] = static::_url($config);
return new static($config);
}
}
src/webroot/index.php
use App\Network\MyCustomRequest;
$dispatcher = DispatcherFactory::create();
$dispatcher->dispatch(
MyCustomRequest::createFromGlobals(), // there goes the custom request
new Response()
);
Upvotes: 1