From 8fd2068f4e2b8b2daed122b750e8ace7cbdf7789 Mon Sep 17 00:00:00 2001
From: Joao M
Date: Sun, 9 Nov 2025 20:54:49 -0300
Subject: [PATCH 1/3] Claude/update legacy config 011 c uy cef h lgxfv x zm77h5
c6 (#18)
* Update legacy configuration files
- Update phpunit.xml.dist to PHPUnit 10+ format with new attributes and source tag
- Create psalm.xml with static analysis configuration
- Update composer.json: PHP >=8.1 <8.5, byjg/jwt-wrapper ^6.0, phpunit ^10|^11, vimeo/psalm ^5.9|^6.12
- Add composer scripts for test and psalm
- Update GitHub workflow to test PHP 8.1-8.4
- Update .gitignore with additional patterns
* Fix compatibility with jwt-wrapper 6.0
- Update namespace imports from ByJG\Util to ByJG\JwtWrapper
- Update class names: JwtKeySecret -> JwtHashHmacSecret, JwtRsaKey -> JwtOpenSSLKey
- Update JwtWrapper::createJwtData() call to pass array instead of string
- Make test data providers static for PHPUnit 10+ compatibility
- Convert @dataProvider annotations to PHP attributes
- Suppress unserialize warnings in session parser (expected behavior)
* Fix psalm static analysis errors
- Add #[Override] attributes to SessionHandlerInterface methods
- Fix gc() return type from bool to int|false per interface
- Remove redundant null coalescing for getCookiePath() calls
---------
Co-authored-by: Claude
---
.github/workflows/phpunit.yml | 6 ++++--
.gitignore | 3 +++
composer.json | 11 ++++++++---
phpunit.xml.dist | 27 +++++++++++++++++----------
psalm.xml | 18 ++++++++++++++++++
src/JwtSession.php | 28 +++++++++++++++++-----------
src/SessionConfig.php | 12 ++++++------
tests/JwtSessionTest.php | 25 ++++++-------------------
8 files changed, 79 insertions(+), 51 deletions(-)
create mode 100644 psalm.xml
diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml
index c4473dc..81a3084 100644
--- a/.github/workflows/phpunit.yml
+++ b/.github/workflows/phpunit.yml
@@ -16,9 +16,10 @@ jobs:
strategy:
matrix:
php-version:
+ - "8.4"
+ - "8.3"
- "8.2"
- "8.1"
- - "8.0"
steps:
- uses: actions/checkout@v4
@@ -32,5 +33,6 @@ jobs:
with:
folder: php
project: ${{ github.event.repository.name }}
- secrets: inherit
+ secrets:
+ DOC_TOKEN: ${{ secrets.DOC_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 13c8a0e..071730b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,6 @@
composer.lock
vendor
/.phpunit.result.cache
+phpunit.coverage.xml
+phpunit.report.xml
+*.bak
diff --git a/composer.json b/composer.json
index ad5b53b..6344eb8 100644
--- a/composer.json
+++ b/composer.json
@@ -9,11 +9,16 @@
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
- "php": ">=8.0",
- "byjg/jwt-wrapper": "4.9.*"
+ "php": ">=8.1 <8.5",
+ "byjg/jwt-wrapper": "^6.0"
},
"require-dev": {
- "phpunit/phpunit": "5.7.*|7.4.*|^9.6"
+ "phpunit/phpunit": "^10|^11",
+ "vimeo/psalm": "^5.9|^6.12"
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "psalm": "vendor/bin/psalm"
},
"license": "MIT"
}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d5df793..0de38db 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -6,14 +6,21 @@ and open the template in the editor.
-->
-
+ displayDetailsOnTestsThatTriggerDeprecations="true"
+ displayDetailsOnTestsThatTriggerErrors="true"
+ displayDetailsOnTestsThatTriggerNotices="true"
+ displayDetailsOnTestsThatTriggerWarnings="true"
+ displayDetailsOnPhpunitDeprecations="true"
+ failOnWarning="true"
+ failOnNotice="true"
+ failOnDeprecation="true"
+ failOnPhpunitDeprecation="true"
+ stopOnFailure="false"
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd">
@@ -21,11 +28,11 @@ and open the template in the editor.
-
-
+
+
./src
-
-
+
+
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..cba57d1
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/JwtSession.php b/src/JwtSession.php
index b417d6d..3afe776 100644
--- a/src/JwtSession.php
+++ b/src/JwtSession.php
@@ -2,8 +2,8 @@
namespace ByJG\Session;
-use ByJG\Util\JwtWrapper;
-use ByJG\Util\JwtWrapperException;
+use ByJG\JwtWrapper\JwtWrapper;
+use ByJG\JwtWrapper\JwtWrapperException;
use Exception;
use SessionHandlerInterface;
@@ -64,6 +64,7 @@ protected function replaceSessionHandler(): void
*
* @since 5.4.0
*/
+ #[\Override]
public function close(): bool
{
return true;
@@ -80,6 +81,7 @@ public function close(): bool
*
* @since 5.4.0
*/
+ #[\Override]
public function destroy(string $id): bool
{
if (!headers_sent()) {
@@ -87,7 +89,7 @@ public function destroy(string $id): bool
self::COOKIE_PREFIX . $this->sessionConfig->getSessionContext(),
"",
(time()-3000),
- $this->sessionConfig->getCookiePath() ?? "",
+ $this->sessionConfig->getCookiePath(),
$this->sessionConfig->getCookieDomain() ?? "",
);
}
@@ -99,19 +101,20 @@ public function destroy(string $id): bool
* Cleanup old sessions
*
* @link http://php.net/manual/en/sessionhandlerinterface.gc.php
+ *
* @param int $max_lifetime
* Sessions that have not updated for
* the last maxlifetime seconds will be removed.
*
- * @return int|false
- * The return value (usually TRUE on success, FALSE on failure).
- * Note this value is returned internally to PHP for processing.
- *
+ *
+ * @return int|false The return value (usually TRUE on success, FALSE on failure). Note this value is returned internally to PHP for processing.
+ *
* @since 5.4.0
*/
+ #[\Override]
public function gc(int $max_lifetime): int|false
{
- return true;
+ return 1;
}
/**
@@ -126,6 +129,7 @@ public function gc(int $max_lifetime): int|false
*
* @since 5.4.0
*/
+ #[\Override]
public function open(string $path, string $name): bool
{
return true;
@@ -143,6 +147,7 @@ public function open(string $path, string $name): bool
*
* @since 5.4.0
*/
+ #[\Override]
public function read(string $id): string
{
try {
@@ -184,13 +189,14 @@ public function read(string $id): string
* @throws JwtWrapperException
* @since 5.4.0
*/
+ #[\Override]
public function write(string $id, string $data): bool
{
$jwt = new JwtWrapper(
$this->sessionConfig->getServerName(),
$this->sessionConfig->getKey()
);
- $session_data = $jwt->createJwtData($data, $this->sessionConfig->getTimeoutMinutes() * 60);
+ $session_data = $jwt->createJwtData(['data' => $data], $this->sessionConfig->getTimeoutMinutes() * 60, 0, null);
$token = $jwt->generateToken($session_data);
if (!headers_sent()) {
@@ -198,7 +204,7 @@ public function write(string $id, string $data): bool
self::COOKIE_PREFIX . $this->sessionConfig->getSessionContext(),
$token,
(time()+$this->sessionConfig->getTimeoutMinutes()*60) ,
- $this->sessionConfig->getCookiePath() ?? "",
+ $this->sessionConfig->getCookiePath(),
$this->sessionConfig->getCookieDomain() ?? "",
false,
true
@@ -236,7 +242,7 @@ public function unSerializeSessionData($session_data): array
$num = $pos - $offset;
$varname = substr($session_data, $offset, $num);
$offset += $num + 1;
- $data = unserialize(substr($session_data, $offset));
+ $data = @unserialize(substr($session_data, $offset), ['allowed_classes' => true]);
$return_data[$varname] = $data;
$offset += strlen(serialize($data));
}
diff --git a/src/SessionConfig.php b/src/SessionConfig.php
index cdcfdc9..54d0d13 100644
--- a/src/SessionConfig.php
+++ b/src/SessionConfig.php
@@ -2,9 +2,9 @@
namespace ByJG\Session;
-use ByJG\Util\JwtKeyInterface;
-use ByJG\Util\JwtKeySecret;
-use ByJG\Util\JwtRsaKey;
+use ByJG\JwtWrapper\JwtKeyInterface;
+use ByJG\JwtWrapper\JwtHashHmacSecret;
+use ByJG\JwtWrapper\JwtOpenSSLKey;
class SessionConfig
{
@@ -53,13 +53,13 @@ public function withCookie($domain, $path = "/"): static
public function withSecret($secret): static
{
- $this->jwtKey = new JwtKeySecret($secret);
+ $this->jwtKey = new JwtHashHmacSecret($secret);
return $this;
}
-
+
public function withRsaSecret($private, $public): static
{
- $this->jwtKey = new JwtRsaKey($private, $public);
+ $this->jwtKey = new JwtOpenSSLKey($private, $public);
return $this;
}
diff --git a/tests/JwtSessionTest.php b/tests/JwtSessionTest.php
index 0cf1a09..5387a17 100644
--- a/tests/JwtSessionTest.php
+++ b/tests/JwtSessionTest.php
@@ -1,10 +1,11 @@
assertTrue($this->object->close());
}
- public function dataProvider(): array
+ public static function dataProvider(): array
{
$obj = new stdClass();
$obj->prop1 = "value1";
@@ -119,35 +120,21 @@ public function dataProvider(): array
];
}
- /**
- * @dataProvider dataProvider
- * @param $input
- * @param $expected
- */
+ #[DataProvider('dataProvider')]
public function testSerializeSessionData($input, $expected)
{
$result = $this->object->serializeSessionData($input);
$this->assertEquals($expected, $result);
}
- /**
- * @dataProvider dataProvider
- * @param $expected
- * @param $input
- * @throws Exception
- */
+ #[DataProvider('dataProvider')]
public function testUnserializeData($expected, $input)
{
$result = $this->object->unSerializeSessionData($input);
$this->assertEquals($expected, $result);
}
- /**
- * @dataProvider dataProvider
- * @param $object
- * @param $serialize
- * @throws JwtWrapperException
- */
+ #[DataProvider('dataProvider')]
public function testReadWrite($object, $serialize)
{
$this->object->write("SESSID", $serialize);
From 68ee7250725449b3fbac084d6300c97a700c7ed1 Mon Sep 17 00:00:00 2001
From: Joao Gilberto Magalhaes
Date: Tue, 18 Nov 2025 18:06:27 -0500
Subject: [PATCH 2/3] Update PHPUnit GitHub Actions workflow with container
options and checkout version
---
.github/workflows/phpunit.yml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml
index 81a3084..2b4481e 100644
--- a/.github/workflows/phpunit.yml
+++ b/.github/workflows/phpunit.yml
@@ -12,7 +12,9 @@ on:
jobs:
Build:
runs-on: 'ubuntu-latest'
- container: 'byjg/php:${{ matrix.php-version }}-cli'
+ container:
+ image: 'byjg/php:${{ matrix.php-version }}-cli'
+ options: --user root --privileged
strategy:
matrix:
php-version:
@@ -22,7 +24,7 @@ jobs:
- "8.1"
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- run: composer install
- run: ./vendor/bin/phpunit
From a7e68370e0d801f2f36b72e8a24587d4aa98b765 Mon Sep 17 00:00:00 2001
From: Joao Gilberto Magalhaes
Date: Sat, 22 Nov 2025 01:43:17 -0500
Subject: [PATCH 3/3] Update GitHub Actions, dependencies, and improve JWT
session handling
- Added Psalm static analysis to GitHub Actions workflow.
- Updated PHP version matrix in workflows to include PHP 8.5 and removed older versions.
- Enhanced session key validation in `JwtSession` methods.
- Adjusted composer constraints for PHP and dependencies.
- Updated `README.md` with revised description and sponsor badge.
- Improved error handling in `JwtSession` implementation.
- Bumped Psalm error level to 3 in configuration for stricter checks.
---
.github/workflows/phpunit.yml | 35 ++++++++++++++++++++++++++++++++---
README.md | 14 +++++++-------
composer.json | 10 +++++-----
psalm.xml | 2 +-
src/JwtSession.php | 17 ++++++++++++++---
tests/JwtSessionTest.php | 2 +-
6 files changed, 60 insertions(+), 20 deletions(-)
diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml
index 2b4481e..fc5f29a 100644
--- a/.github/workflows/phpunit.yml
+++ b/.github/workflows/phpunit.yml
@@ -18,15 +18,44 @@ jobs:
strategy:
matrix:
php-version:
+ - "8.5"
- "8.4"
- "8.3"
- - "8.2"
- - "8.1"
steps:
- uses: actions/checkout@v5
- run: composer install
- - run: ./vendor/bin/phpunit
+ - run: composer test
+
+ Psalm:
+ name: Psalm Static Analyzer
+ runs-on: ubuntu-latest
+ permissions:
+ # for github/codeql-action/upload-sarif to upload SARIF results
+ security-events: write
+ container:
+ image: byjg/php:8.4-cli
+ options: --user root --privileged
+
+ steps:
+ - name: Git checkout
+ uses: actions/checkout@v4
+
+ - name: Composer
+ run: composer install
+
+ - name: Psalm
+ # Note: Ignoring error code 2, which just signals that some
+ # flaws were found, not that Psalm itself failed to run.
+ run: ./vendor/bin/psalm
+ --show-info=true
+ --report=psalm-results.sarif || [ $? = 2 ]
+
+ - name: Upload Analysis results to GitHub
+ uses: github/codeql-action/upload-sarif@v4
+ if: github.ref == 'refs/heads/master'
+ with:
+ sarif_file: psalm-results.sarif
Documentation:
if: github.ref == 'refs/heads/master'
diff --git a/README.md b/README.md
index c92de21..421c5cd 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
-# JwtSession
+# JWT Session Handler
-[](https://github.com/byjg/jwt-session/actions/workflows/phpunit.yml)
-[](http://opensource.byjg.com)
-[](https://github.com/byjg/jwt-session/)
-[](https://opensource.byjg.com/opensource/licensing.html)
+[](https://github.com/sponsors/byjg)
+[](https://github.com/byjg/jwt-session/actions/workflows/phpunit.yml)
+[](http://opensource.byjg.com)
+[](https://github.com/byjg/jwt-session/)
+[](https://opensource.byjg.com/opensource/licensing.html)
[](https://github.com/byjg/jwt-session/releases/)
-JwtSession is a PHP session replacement. Instead of use FileSystem, just use JWT TOKEN.
-The implementation following the SessionHandlerInterface.
+A PHP session replacement that stores session data in JWT tokens instead of the filesystem. This implementation follows the SessionHandlerInterface standard, enabling stateless sessions without the need for dedicated session servers like Redis or Memcached. Perfect for distributed applications and microservices architectures.
# How to use:
diff --git a/composer.json b/composer.json
index 6344eb8..ac59705 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
{
"name": "byjg/jwt-session",
- "description": "JwtSession is a PHP session replacement. Instead of use FileSystem, just use JWT TOKEN. The implementation following the SessionHandlerInterface.",
+ "description": "A PHP session replacement that stores session data in JWT tokens instead of the filesystem. This implementation follows the SessionHandlerInterface standard, enabling stateless sessions without the need for dedicated session servers like Redis or Memcached. Perfect for distributed applications and microservices architectures.",
"autoload": {
"psr-4": {
"ByJG\\Session\\": "src/"
@@ -9,16 +9,16 @@
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
- "php": ">=8.1 <8.5",
+ "php": ">=8.3 <8.6",
"byjg/jwt-wrapper": "^6.0"
},
"require-dev": {
- "phpunit/phpunit": "^10|^11",
- "vimeo/psalm": "^5.9|^6.12"
+ "phpunit/phpunit": "^10.5|^11.5",
+ "vimeo/psalm": "^5.9|^6.13"
},
"scripts": {
"test": "vendor/bin/phpunit",
- "psalm": "vendor/bin/psalm"
+ "psalm": "vendor/bin/psalm --threads=1"
},
"license": "MIT"
}
diff --git a/psalm.xml b/psalm.xml
index cba57d1..241b742 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -1,6 +1,6 @@
sessionConfig->getSessionContext()])) {
+ $key = $this->sessionConfig->getKey();
+ if ($key === null) {
+ return '';
+ }
$jwt = new JwtWrapper(
$this->sessionConfig->getServerName(),
- $this->sessionConfig->getKey()
+ $key
);
$data = $jwt->extractData($_COOKIE[self::COOKIE_PREFIX . $this->sessionConfig->getSessionContext()]);
@@ -192,9 +196,13 @@ public function read(string $id): string
#[\Override]
public function write(string $id, string $data): bool
{
+ $key = $this->sessionConfig->getKey();
+ if ($key === null) {
+ return false;
+ }
$jwt = new JwtWrapper(
$this->sessionConfig->getServerName(),
- $this->sessionConfig->getKey()
+ $key
);
$session_data = $jwt->createJwtData(['data' => $data], $this->sessionConfig->getTimeoutMinutes() * 60, 0, null);
$token = $jwt->generateToken($session_data);
@@ -239,6 +247,9 @@ public function unSerializeSessionData($session_data): array
while ($offset < strlen($session_data)) {
if (!str_contains(substr($session_data, $offset), "|")) throw new JwtSessionException("invalid data, remaining: " . substr($session_data, $offset));
$pos = strpos($session_data, "|", $offset);
+ if ($pos === false) {
+ throw new JwtSessionException("invalid data, pipe not found");
+ }
$num = $pos - $offset;
$varname = substr($session_data, $offset, $num);
$offset += $num + 1;
diff --git a/tests/JwtSessionTest.php b/tests/JwtSessionTest.php
index 5387a17..47fbb82 100644
--- a/tests/JwtSessionTest.php
+++ b/tests/JwtSessionTest.php
@@ -22,7 +22,7 @@ class JwtSessionTest extends TestCase
*/
protected SessionConfig $sessionConfig;
- const SESSION_ID = "sessionid";
+ const string SESSION_ID = "sessionid";
/**
* @throws JwtSessionException