Skip to main content

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

FieldTypeDescription
issstringAlways auth.vera.id
substringPublisher-specific user hash. Some Publisher sees a different hash than Other Publisher (no cross-publisher tracking).
audstringPublisher domain. Token is only valid for this domain.
iat / expunix timestampIssued-at and expiry. TTL: 1 hour.
jtistringOne-time token ID. Replay protection.
vera.subscriptionsstring[]List of active subscription keys.
vera.ppr_balancefloatCurrent pay-per-read balance in EUR.
vera.consent_scopestring[]Consent categories granted by the user.
vera.network_verifiedbooleanNetwork verification signal present and valid.
vera.human_scorefloat0.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