Skip to content

Instantly share code, notes, and snippets.

@mscdex
Last active September 15, 2023 11:55
Show Gist options
  • Save mscdex/9507b0d8df42e0aec825 to your computer and use it in GitHub Desktop.
Save mscdex/9507b0d8df42e0aec825 to your computer and use it in GitHub Desktop.
sharing sessions between node.js and php using redis
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");
}
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>";
?>
@tuxbotix
Copy link

Just as @ramin-git mentioned, I also experienced the issue of express-session not accepting cookie by PHP but other way works.

The issue happens when a new session start, the calls for session_regenerate_id(); (line 45) to (re)generate a new ID for the new session (and cookie). But the next call for session_id() returns empty since both these functions should be called after session_start().

Fix:

if you are 100% sure that regenerate_id() will not work before session_start() (I'm not completely sure :P), then just replace line 45 and 46 with the following, that way executing regenerate and asking for a session id is avoided. This method should work definitely but I have to do further testing.

$sessHandlerInst=new SessionHandler();
$sess_id=$sessHandlerInst->create_sid();

or append the following instead, between line 46 and line 47 (after $sess_id = session_id();, before $hmac = str_replace.... - this will generate a new ID if $sess_id turns out empty (which will be*).

if(!$sess_id){
            $sessHandlerInst=new SessionHandler();
            $sess_id=$sessHandlerInst->create_sid();
}

Explanation :

Since the expectation for calling

 session_regenerate_id();
 $sess_id = session_id();

is to generate a new ID which the script modify to match express-session format, calling SessionHandler::create_sid() can be used. Yes the session_regenerate() internally calls create_sid() but will only work if the session has been started (which is not* otherwise setting session ID is not possible).

@rustigano
Copy link

what is the best approach for using this with multiple domains?
Example of the domains that need to be aware of the session:

  • domain.com
  • sub1.domain.com
  • bla.domain.com

I have tried adding the domain to the cookie (prefixed with and without a dot) but no success yet.

Same problem is in this thread: https://github.com/expressjs/session/issues/633

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment