Authentication & Token System
Early Draft
This specification is at an early draft stage. Ideas are open for change and debate. A lot of the content was developed with the help of Claude AI.
Authentication & Token System
JWT Structure (X-Vera-Token)
The token is an ES256-signed JWT issued by the Vera identity provider. It contains only anonymized, aggregated data: no real name, no email address.
{
"header": {
"alg": "ES256",
"typ": "JWT",
"kid": "vera-2025-04"
},
"payload": {
"iss": "auth.vera.id",
"sub": "usr_hash_a3f9b2c1",
"aud": "some-publisher.news",
"iat": 1712234400,
"exp": 1712238000,
"jti": "tok_7f3a9b2c_once",
"vera": {
"subscriptions": ["spiegel", "zeit", "handelsblatt"],
"ppr_balance": 4.80,
"consent_scope": ["analytics", "personalization"],
"network_verified": true,
"human_score": 0.99
}
}
}
Payload Fields
| Field | Type | Description |
|---|---|---|
iss | string | Always auth.vera.id |
sub | string | Publisher-specific user hash. Some Publisher sees a different hash than Other Publisher (no cross-publisher tracking). |
aud | string | Publisher domain. Token is only valid for this domain. |
iat / exp | unix timestamp | Issued-at and expiry. TTL: 1 hour. |
jti | string | One-time token ID. Replay protection. |
vera.subscriptions | string[] | List of active subscription keys. |
vera.ppr_balance | float | Current pay-per-read balance in EUR. |
vera.consent_scope | string[] | Consent categories granted by the user. |
vera.network_verified | boolean | Network verification signal present and valid. |
vera.human_score | float | 0.0–1.0. Composite score from network verification + browser heuristics. |
Token Validation (Publisher Side)
import jwt
from vera_sdk import fetch_vera_public_keys
# Public keys fetched from https://auth.vera.id/.well-known/vera-keys
# Cached hourly
VERA_PUBLIC_KEYS = fetch_vera_public_keys()
def validate_vera_token(token: str, expected_audience: str) -> dict | None:
try:
claims = jwt.decode(
token,
key=VERA_PUBLIC_KEYS,
algorithms=["ES256"],
audience=expected_audience,
options={"require": ["exp", "iat", "sub", "jti", "vera"]}
)
# JTI replay check (Redis or equivalent)
if is_jti_used(claims["jti"]):
return None
mark_jti_used(claims["jti"], ttl=3600)
return claims
except jwt.InvalidTokenError:
return None
Token Lifecycle
User starts Vera
|
v
Vera SSO (once, browser-native, PKCE flow)
|
v
Vera holds session token (local, encrypted)
|
v
Per navigation: issue short-lived request token
-> audience = current domain
-> jti = fresh UUID
-> TTL = 1 hour
|
v
Attach as X-Vera-Token header
|
v
After 1h: silent refresh via Vera SSO