a4sex / self-signed-token
A simple self-signed token based on a secret string and creation time.
Requires
- php: ^8.0
- symfony/uid: ^7.3
Requires (Dev)
- phpunit/phpunit: ^9.0
README
Simple, stateless, and secure tokens signed solely with a secret — no database, no network calls.
Table of contents
- About
- Installation
- Quick start
- Token structure
- Time modes and TTL
- Public API
- Token validation
- Manual signature verification
- Helpers and utilities
- Key management (KeyStore)
- Requirements
- License
About
Self‑Signed Token
produces self-signed tokens composed of the following parts:
- an identifier (ID) with optional ID-section data;
- a timestamp and arbitrary payload in the second section;
- a cryptographic signature with an explicit algorithm (except for
md5
); - a meta section (time interpretation type and optional
keyId
).
Validation is fully local using the secret. No DB state is stored or queried. The library also includes a key management (KeyStore) module for generating, rotating, and storing secrets in a file.
Installation
composer require a4sex/self-signed-token
Quick start
use A4Sex\SelfSignedToken;
$tokenService = new SelfSignedToken(
ttl: 60,
secret: 'my-secret',
options: [
'algo' => 'md5', // md5|sha256|sha512|sha3-256|sha3-512
'type' => 'expired', // expired|created
'keyId' => null, // optional key identifier
'bypass' => false, // globally skip checks
]
);
// Create a token
$token = $tokenService->create(payload: ['role','admin']);
// Validate token (signature and expiration)
$isValid = $tokenService->valid($token);
Token structure
<id[:idData...]>.<time[:data...]>.<signSection>.<metaSection>
- ID section:
id[:idData...]
- PAYLOAD section:
time[:data1[:data2...]]
- SIGN section: if the algorithm is
md5
, it contains only<hash>
; otherwise<algo>:<hash>
- META section:
keyId[:type[:version...]]
or justtype
(defaults toexpired
)
Examples
user42.1716309450:role:admin.sha256:1cb7e4d8....created
id1:x.1700000000:foo.sha256:deadbeef.kid-1:created:v1
id2.1700000000.abcd.expired
Time modes and TTL
- expired: the second section stores the expiration moment. If
expire
is not provided on create, it is computed asnow + TTL
. - created: the second section stores the creation time. Expiration is
created + TTL
.
Switch modes via the type
option.
Public API
use A4Sex\SelfSignedToken;
use A4Sex\Entity\Token as TokenEntity;
$t = new SelfSignedToken(ttl: 120, secret: 's3cr3t', options: [
'algo' => 'sha256',
'type' => 'created',
'keyId' => 'kid-1',
]);
// Generate ID (md5 based on secret + randomness)
$id = $t->generateId('prefix-');
// Create token
$token = $t->create(id: $id, expire: null, payload: ['role','admin']);
// Validate: returns token ID or false
$idOrFalse = $t->valid($token, $ignoreSign = false, $ignoreExpires = false);
// Parse into entity
$entity = $t->parse($token); // TokenEntity
// Individual checks
$t->signed($entity); // true|false
$t->expired($entity); // true|false
// Control time/TTL
$t->setCreated(time()); // fix the "current" time
$t->setTtl(300); // change TTL
// Global bypass (disable checks)
$t->setBypass(true);
Fields available after parse()
:
TokenEntity::id()
— token IDTokenEntity::time()
— time value from the PAYLOAD sectionTokenEntity::payload()
— payload array of stringsTokenEntity::sign()
— signatureTokenEntity::alg()
— signature algorithmTokenEntity::keyId()
—keyId
from META section (if any)TokenEntity::type()
— type (expired
|created
)TokenEntity::version()
— version (if any)
Token validation
$ignoreSignature = false;
$ignoreExpire = false;
$isValid = $tokenService->valid($token, $ignoreSignature, $ignoreExpire);
You can disable all checks globally with setBypass(true)
.
Manual signature verification
use A4Sex\Services\Signer;
$entity = $tokenService->parse($token);
// Canonical string to sign: id:payload[:keyId]:secret
$canonical = $tokenService->signature($entity, 'my-secret');
$ok = Signer::verify($canonical, $entity->sign(), $entity->alg());
Helpers and utilities
Signer: create and verify signatures.
- Supported algorithms:
md5
,sha256
,sha512
,sha3-256
,sha3-512
. Example:
use A4Sex\Services\Signer; $hash = Signer::sign('data', 'sha512'); $ok = Signer::verify('data', $hash, 'sha512');
- Supported algorithms:
TokenSections / entity sections: low-level classes to work with token parts (
TokenIdSection
,TokenPayloadSection
,TokenSignSection
,TokenMetaSection
).TrainParser: build and parse
A:B:C
strings + JSON round-trip helpers.IdGenerator: simple random identifier based on
md5(salt + uniqid(...))
.UuidGenerator: generate
uuid4
,uuid6
,ulid
.A9Generator: fixed-length alphanumeric IDs (A–Z, 0–9) without collisions within a provided list.
Additionally: see docs/styleguide.md
for token style guidance and docs/keystore.md
for key management details.
Key management (KeyStore)
A small file-backed KeyStore to manage signing secrets:
- Key: key model (
id
,secret
,created
,payload[]
). - KeySet: a set of keys keyed by
id
. - KeyStorage: load/save
KeySet
from/to a text file. Line format:ID:SECRET[:CREATED[:PAYLOAD...]]
. - KeyGenerator: generate
id
(viaA9Generator
, default length 5) andsecret
(viaIdGenerator
). - KeyManager: high-level operations —
add
,rotate
,revoke
with auto-persist viaKeyStorage
.
Example:
use A4Sex\Key\KeyStorage;
use A4Sex\Key\KeyGenerator;
use A4Sex\KeyManager;
$storage = new KeyStorage(__DIR__ . '/keys.txt');
$manager = new KeyManager($storage);
// Create and add a new key
$generator = new KeyGenerator();
$key = $generator->generate(payload: ['scope','auth']);
$manager->add($key);
// Rotate secret for the existing id
$rotated = $manager->rotate($key->id());
Requirements
- PHP ≥ 8.1
ext-hash
extensionsymfony/uid
package (forUuidGenerator
)
License
MIT licensed. See LICENSE.