# ppoppo-token
JWT (RFC 9068, EdDSA) issuance and verification engine for the Ppoppo ecosystem.
A **single deep module** that hides RFC 8725 mitigations M01–M45, JWKS handling,
algorithm pinning, header/claim/domain checks, and substrate ports (epoch,
session, replay) behind a two-function surface: `issue` and `verify`.
## Quick start — verifier (most consumers)
```toml
[dependencies]
ppoppo-token = "0.1"
```
```rust
use ppoppo_token::{verify, KeySet, VerifyConfig, AuthError};
async fn check_bearer(token: &str, key_set: &KeySet) -> Result<(), AuthError> {
let cfg = VerifyConfig::access_token("accounts.ppoppo.com", "my-client-id");
let claims = verify(token, &cfg, key_set).await?;
println!("verified token for ppnum_id={}", claims.sub);
Ok(())
}
```
The single `verify` entry-point funnels every flow through one engine — no
direct `jsonwebtoken` calls outside the engine, no parallel decode paths,
and no transport-aware branches (M38 invariant: cookie and `Bearer`-header
are the same surface; consumer middleware extracts the bare token before
calling `verify`).
## What is hidden
The engine owns the algorithm. Consumers never:
- Decode JWT segments or pick an `alg` — the engine pins `EdDSA` (sealed
`Algorithm` enum, M51/M52/M54 structurally uncompilable outside the engine).
- Manage replay caches, session liveness, or per-account epoch checks —
these are *substrate ports* (`ReplayDefense`, `SessionRevocation`,
`EpochRevocation`); pass an implementation in `VerifyConfig`.
- Parse JWKS by hand — `Jwks::into_key_set` builds a `KeySet`, and
`KeySet` rotates keys without consumer involvement.
## Quick start — issuer (PAS only)
```rust
use std::time::Duration;
use ppoppo_token::{issue, IssueConfig, IssueRequest, SigningKey};
let key = SigningKey::from_ed25519_pem(pem_bytes, "kid-2026-05-05")?;
let cfg = IssueConfig::access_token(
"accounts.ppoppo.com",
"my-client-id",
"kid-2026-05-05",
);
let req = IssueRequest::new(ppnum_id, "my-client-id", Duration::from_secs(3600));
let token: String = issue(&req, &cfg, &key)?;
```
Issuance is intentionally synchronous (no I/O) and lives in the same engine —
adding a parallel issuance path would fork the algorithm and silently allow
header drift between issuers and verifiers.
## Shared cache contract (cross-substrate)
When multiple processes verify tokens against a shared `session_version`
cache, all readers/writers MUST agree on the key shape and TTL:
```rust
use ppoppo_token::{sv_cache_key, SV_CACHE_TTL};
let key = sv_cache_key("01HK123ABCDEF456GHJ789KLMN"); // → "sv:01HK..."
// TTL contract: 60 seconds. Bounds post-break-glass staleness window.
```
Format-coupling the cache key through the engine prevents drift between PAS
(writer) and consumers (PCS chat-auth, the `pas-external` SDK) — no string
duplication on the consumer side.
## Stability
- `0.x`: pre-1.0 SemVer. Minor bumps may break the public surface.
- The engine surface (`verify`, `issue`, `Claims`, `Algorithm`,
`VerifyConfig`, `IssueConfig`, `IssueRequest`, `KeySet`, `Jwks`,
`AuthError`, `IssueError`) is the durable contract.
- Substrate port traits (`ReplayDefense`, `SessionRevocation`,
`EpochRevocation`) may add methods in 0.x; pin a minor version if you
implement them outside this workspace.
## License
MIT.