a4sex/self-signed-token

A simple self-signed token based on a secret string and creation time.

v2.2.2 2025-08-18 10:46 UTC

README

Simple, stateless, and secure tokens signed solely with a secret — no database, no network calls.

Packagist PHP License

Table of contents

  1. About
  2. Installation
  3. Quick start
  4. Token structure
  5. Time modes and TTL
  6. Public API
  7. Token validation
  8. Manual signature verification
  9. Helpers and utilities
  10. Key management (KeyStore)
  11. Requirements
  12. 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 just type (defaults to expired)

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 as now + 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 ID
  • TokenEntity::time() — time value from the PAYLOAD section
  • TokenEntity::payload() — payload array of strings
  • TokenEntity::sign() — signature
  • TokenEntity::alg() — signature algorithm
  • TokenEntity::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');
      
  • 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 (via A9Generator, default length 5) and secret (via IdGenerator).
  • KeyManager: high-level operations — add, rotate, revoke with auto-persist via KeyStorage.

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 extension
  • symfony/uid package (for UuidGenerator)

License

MIT licensed. See LICENSE.