AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
스토리지 없이 PSR-7 세션을 지원합니다
:mailbox_with_mail: storage-less PSR-7 session support
개발 도구PHP · MakefileGitHub @psr7-sessions직접 설치운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗PSR7Session is a PSR-7 and PSR-15 compatible middleware that enables session without I/O usage in PSR-7 based applications.
Proudly brought to you by ocramius, malukenho and lcobucci.
composer require psr7-sessions/storageless
You can use the PSR7Sessions\Storageless\Http\SessionMiddleware in any
PSR-15
compatible middleware.
In a mezzio/mezzio
application, this would look like following:
use Lcobucci\JWT\Configuration as JwtConfig;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Key\InMemory;
use PSR7Sessions\Storageless\Http\SessionMiddleware;
use PSR7Sessions\Storageless\Http\Configuration as StoragelessConfig;
$app = new \Mezzio\Application(/* ... */);
$app->pipe(new SessionMiddleware(
StoragelessConfig::fromJwtConfiguration(
JwtConfig::forSymmetricSigner(
new Signer\Hmac\Sha256(),
InMemory::base64Encoded((string) getenv('SESSION_SIGNING_KEY')), // see below on how to generate it
)
)
));
After this, you can access the session data inside any middleware that
has access to the Psr\Http\Message\ServerRequestInterface attributes:
$app->get('/get', function (ServerRequestInterface $request, ResponseInterface $response) : ResponseInterface {
$session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
assert($session instanceof \PSR7Sessions\Storageless\Session\SessionInterface);
$session->set('counter', $session->get('counter', 0) + 1);
$response
->getBody()
->write('Counter Value: ' . $session->get('counter'));
return $response;
});
You can do this also in asynchronous contexts and long-running processes, since no super-globals nor I/O are involved.
Anyone knowing the signing key can forge any session, so the key must be
kept secret, outside of your codebase (for example in an environment variable),
and generated using a cryptographically secure pseudo-random number generator
(CSPRNG). Never use a key found in documentation or examples.
For the Sha256 signer above, you can generate a key with:
php -r 'echo base64_encode(random_bytes(32)), PHP_EOL;'
An empty key is rejected, so a missing environment variable makes the configuration fail instead of silently using a weak key.
Note that you can also use asymmetric keys; please refer to
lcobucci/jwt documentation:
Configuration object: https://lcobucci-jwt.readthedocs.io/en/stable/configuration/To mitigate the risks associated to cookie stealing and thus
session hijacking,
you can bind the user session to its IP ($_SERVER['REMOTE_ADDR']) and
User-Agent ($_SERVER['HTTP_USER_AGENT']) by enabling client fingerprinting:
use PSR7Sessions\Storageless\Http\SessionMiddleware;
use PSR7Sessions\Storageless\Http\Configuration as StoragelessConfig;
use PSR7Sessions\Storageless\Http\ClientFingerprint\Configuration as FingerprintConfig;
$app = new \Mezzio\Application(/* ... */);
$app->pipe(new SessionMiddleware(
StoragelessConfig::fromJwtConfiguration(/* ... */)
->withClientFingerprintConfiguration(
FingerprintConfig::forIpAndUserAgent()
)
));
When a request lacks any of the fingerprint sources (for example, it has no
User-Agent header), its session cookie is ignored and no session cookie is
sent back to it: such a client is served an empty session that is never persisted.
If no session ever persists, check that your server provides all the configured
sources, such as $_SERVER['REMOTE_ADDR'].
If your PHP service is behind a reverse proxy of yours, you may need to retrieve the client IP from a different source of truth.
In such cases you can extract the information you need by writing a custom
\PSR7Sessions\Storageless\Http\ClientFingerprint\Source implementation.
Headers such as X-Real-IP or X-Forwarded-For are sent by the client as well:
only trust them when the request comes from your own reverse proxy, and make sure
that the proxy overwrites them instead of appending to the values sent by the client.
Otherwise, an attacker that stole a session cookie can simply send the header with
the victim's IP:
use Psr\Http\Message\ServerRequestInterface;
use PSR7Sessions\Storageless\Http\SessionMiddleware;
use PSR7Sessions\Storageless\Http\Configuration as StoragelessConfig;
use PSR7Sessions\Storageless\Http\ClientFingerprint\Configuration as FingerprintConfig;
use PSR7Sessions\Storageless\Http\ClientFingerprint\Source;
use PSR7Sessions\Storageless\Http\ClientFingerprint\SourceMissing;
use PSR7Sessions\Storageless\Http\ClientFingerprint\UserAgent;
final class ClientIpBehindTrustedProxy implements Source
{
/** @param list<non-empty-string> $trustedProxies IPs of your own reverse proxies */
public function __construct(private readonly array $trustedProxies)
{
}
public function extractFrom(ServerRequestInterface $request): string
{
$remoteAddr = $request->getServerParams()['REMOTE_ADDR'] ?? '';
if (! is_string($remoteAddr) || $remoteAddr === '') {
throw SourceMissing::for('REMOTE_ADDR');
}
if (! in_array($remoteAddr, $this->trustedProxies, true)) {
// not coming from your proxy: the peer itself is the client
return $remoteAddr;
}
$clientIp = $request->getHeaderLine('X-Real-IP');
if ($clientIp === '') {
throw SourceMissing::for('X-Real-IP');
}
return $clientIp;
}
}
$app = new \Mezzio\Application(/* ... */);
$app->pipe(new SessionMiddleware(
StoragelessConfig::fromJwtConfiguration(/* ... */)
->withClientFingerprintConfiguration(
FingerprintConfig::forSources(
new ClientIpBehindTrustedProxy(['10.0.0.10']),
new UserAgent(),
)
)
));
Simply browse to the examples directory in your console, then run
SESSION_SIGNING_KEY="$(php -r 'echo base64_encode(random_bytes(32));')" php -S localhost:9999 index.php
Then try accessing http://localhost:9999: you should see a counter
that increases at every page refresh
In most PHP+HTTP related projects, ext/session serves its purpose and
allows us to store server-side information by associating a certain
identifier to a visiting user-agent.
ext/session?This is all fair and nice, except for:
$_SESSION superglobal$_SESSION for you, and there are security implications)This project tries to implement storage-less sessions and to mitigate the issues listed above.
512 bytes, when JSON encoded
(see the limitations documentation)JsonSerializable or equivalentSession data is directly stored inside a session cookie as a JWT token.
This approach is not new, and is commonly used with Bearer tokens in
HTTP/REST/OAuth APIs.
In order to guarantee that the session data is not modified, that the client can trust the information and that the expiration date is mutually agreed between server and client, a JWT token is used to transmit the information.
The JWT token is always signed to ensure that the user-agent is never able to manipulate the session. Both symmetric and asymmetric keys are supported for signing/verifying tokens.
Please refer to the configuration documentation.
Please refer to the limitations documentation.
Please refer to the contributing notes.
This project is made public under the MIT LICENSE.