S2S JWT Auth Contract (Hyper Lite -> Hyper Core)
Status: Updated for CORE-2005 (March 13, 2026)
Purpose
This document defines the machine-to-machine JWT contract for Hyper Lite server calls into Hyper Core integration endpoints.
Token header
Required protected header fields:
alg:RS256typ:JWTkid: active signing key id
Claims
Required claims:
isssubaudexpiatnbfscopejti
Claim conventions:
scopeuses a space-delimited string (OAuth-style), for example"spaces:create join_tokens:issue".Supported scopes for CORE-2003:
spaces:createjoin_tokens:issue
Maximum TTL:
300seconds.Clock skew tolerance:
60seconds.
Auth response mapping
401 Unauthorizedmissing/invalid/expired token
malformed token/header/claims
unknown
kidinvalid
issoraudreplayed
jti(see Replay protection)
403 Forbiddensignature/claims are valid, but required scope is missing
Shared executable test vectors
Canonical vectors live at:
docs/contracts/s2s-jwt-auth-test-vectors.json
The vectors include:
valid tokens for each required scope
unknown
kidinvalid
audmissing
scopeexpired token
not-yet-valid token
malformed token
insufficient scope (
403)
Both repos should treat this file as the compatibility source of truth for auth behavior.
Replay protection
Each S2S JWT must carry a unique jti (JWT ID) claim. Hyper Core enforces single-use semantics:
On first presentation the
jtiis recorded insession.s2s_jwt_replay.A second request bearing the same
jtiis rejected as401 Unauthorized(reason:replayed_token).Records are retained for 24 hours after the token's
exptimestamp.Expired records are purged every 5 minutes.
Hyper Lite generates a unique jti (nanoid) per S2S request, so replay rejection should never occur under normal operation. A spike in replayed_token rejections indicates either a network-level replay attack or a bug causing token reuse.
Key rotation procedure
kid naming convention: lite-YYYY-MM (e.g., lite-2026-03).
Generate a new RSA keypair and assign a new
kid.Add the new public key to Core's
HYPER_S2S_JWT_PUBLIC_KEYS_JSONkeyset (both old and newkidpresent).Deploy Core — it now accepts tokens signed with either key.
Update Hyper Lite's
HYPER_S2S_JWT_PRIVATE_KEY_PEMandHYPER_S2S_JWT_KIDto the new key.Deploy Hyper Lite — during rolling deploy some workers sign with the old key, some with the new. Core accepts both.
After all Hyper Lite workers are updated (verify via metrics showing only the new
kid), remove the old public key from Core's keyset.Deploy Core again.
Maximum dual-key window: Hyper Lite deploy rollout time + 300 s max token TTL.
To revoke a key immediately: remove it from Core's keyset and deploy. In-flight tokens signed with the revoked key will fail with 401 unknown_kid until they expire (at most 300 s).
Auth observability
Metrics
Hyper Core emits three counters via metrics::counter!(), forwarded to ClickHouse and admin dashboard:
s2s_auth_success
reason, path, scope
Successful S2S JWT authorization
s2s_auth_401
reason, path, scope
Rejected — missing, invalid, expired, replayed, or unknown-kid token
s2s_auth_403
reason, path, scope
Rejected — valid token but missing required scope
Notable reason values for s2s_auth_401:
replayed_token—jtialready seenunknown_kid—kidnot in keysetexpired_signature— token pastexp + clock_skewinvalid_issuer,invalid_audience— claim mismatchmissing_claim(X)— required claim absent
Where metrics surface
Prometheus:
/metricsendpoint (all counters available for scraping).ClickHouse: forwarded via
ServerMetricsForwarderfor historical queries.Admin dashboard: live feed via broadcast channel.
Alert conditions to configure
s2s_auth_401 spike, reason=replayed_token
Replay attack or token-reuse bug
Check Hyper Lite jti generation, network replay indicators
s2s_auth_401 spike, reason=unknown_kid
Key rotation misconfiguration or stale deploy
Verify Core keyset contains expected kid values
s2s_auth_403 spike
Scope misconfiguration on Hyper Lite
Check HYPER_S2S_JWT_SCOPE_* env vars
s2s_auth_success drops to zero
Integration broken
Check Hyper Lite logs at meetings.hypervideo.auth, verify connectivity
Hyper Lite side
Structured logs at logger category
meetings.hypervideo.authcapture S2S auth rejections with actor context, endpoint, scope, and Hypervideo request ID.ClientErrorObservabilityReportercaptures client-side join token refresh failures in the browser.Both flow through the log-tail worker to Analytics Engine for querying.
Operational notes
Signing key material remains private to Hyper Lite.
Hyper Core should only receive/consume public keys keyed by
kid.Raw JWTs must never be logged.
Last updated