Tokens & JWT
HelloJohn issues EdDSA-signed JWTs as access tokens and opaque refresh tokens. Learn the token structure, payload fields, verification process, and rotation strategy.
HelloJohn uses two token types: short-lived access tokens (JWTs) for authorizing requests, and long-lived refresh tokens (opaque) for obtaining new access tokens.
Token types
| Access Token | Refresh Token | |
|---|---|---|
| Format | JWT (EdDSA-signed) | Opaque string |
| Default lifetime | 15 minutes | 30 days |
| Storage | Memory (in SDKs) | Secure storage / HttpOnly cookie |
| Verified | Locally — no API call needed | Server-side only |
| Revocable | Not immediately (stateless) | Immediately (stored in DB) |
| Sent to | Your backend (Authorization header) | HelloJohn only (refresh endpoint) |
JWT structure
HelloJohn access tokens are standard JWTs. You can inspect any token at jwt.io.
Header
{
"alg": "EdDSA",
"kid": "key_01HABCDEF123456",
"typ": "JWT"
}| Field | Value | Description |
|---|---|---|
alg | EdDSA | Ed25519 signing algorithm |
kid | key_01HABCDEF... | Key ID — matches a key in JWKS |
typ | JWT | Standard token type |
Payload
{
"sub": "usr_01HABCDEF123456",
"tenant_id": "tnt_01HABCDEF654321",
"client_id": "cli_01HABCDEF111222",
"email": "user@example.com",
"name": "Alice Smith",
"session_id": "ses_01HABCDEF999888",
"role": "member",
"org_id": "org_01HABCDEF777666",
"mfa_verified": true,
"custom": {
"plan": "pro",
"team_id": "team_123"
},
"iat": 1700000000,
"exp": 1700000900
}| Claim | Type | Description |
|---|---|---|
sub | string | User ID — use as the primary user identifier in your backend |
tenant_id | string | Tenant this user belongs to |
client_id | string | OAuth2 client that issued the token |
email | string | User's email address |
name | string | User's display name |
session_id | string | Session ID — use for session revocation |
role | string | User's global role (super_admin, admin, member) |
org_id | string | Active organization ID (null if not in org context) |
mfa_verified | boolean | Whether the user passed MFA in this session |
custom | object | Custom claims set via tenant configuration |
iat | number | Issued at (Unix timestamp) |
exp | number | Expiry (Unix timestamp) |
Custom claims are added to the JWT payload via tenant configuration. Keep them small — JWTs are sent with every request. See Custom Claims →.
Signature algorithm: EdDSA (Ed25519)
HelloJohn uses EdDSA with the Ed25519 curve for JWT signing. Compared to RS256:
| EdDSA (Ed25519) | RS256 | |
|---|---|---|
| Key size | 32 bytes | 2048+ bits |
| Signature size | 64 bytes | 256 bytes |
| Verification speed | ~5× faster | Baseline |
| Security level | ~128-bit | ~112-bit (2048-bit key) |
JWKS endpoint
Your backend fetches the public key from the JWKS endpoint:
GET https://your-hellojohn-instance.com/.well-known/jwks.jsonResponse:
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "base64url-encoded-public-key",
"kid": "key_01HABCDEF123456",
"use": "sig",
"alg": "EdDSA"
}
]
}All HelloJohn SDKs handle JWKS fetching, caching, and key rotation automatically. For manual verification, see Token Verification →.
Key rotation
HelloJohn supports zero-downtime key rotation. The JWKS endpoint always returns all active keys. When a new key is generated:
- The new key is added to JWKS (alongside the old key)
- New tokens are issued with the new key (
kidin header) - Tokens issued with the old key remain verifiable until expiry
- After the access token max lifetime (15 min default), the old key can be removed
SDKs re-fetch JWKS on kid mismatch, so key rotation is transparent to your application.
Token lifetime configuration
Configure token lifetimes per tenant via environment variable or API:
# Access token lifetime (default: 15m)
HELLOJOHN_ACCESS_TOKEN_TTL=15m
# Refresh token lifetime (default: 30d)
HELLOJOHN_REFRESH_TOKEN_TTL=30dOr via the API:
curl -X PATCH https://your-instance.com/cp/v1/tenants/tnt_01HABCDEF \
-H "Authorization: Bearer {cp_admin_token}" \
-d '{"access_token_ttl": "15m", "refresh_token_ttl": "7d"}'Refresh token rotation
HelloJohn uses refresh token rotation: every time a refresh token is used, it's replaced with a new one. The old refresh token is immediately invalidated.
Client HelloJohn
│ │
│── POST /v1/auth/refresh ────────►
│ Body: { refresh_token: RT1 } │
│ │ Invalidate RT1
│ │ Issue new access_token + RT2
│◄─ { access_token, refresh_token: RT2 } ─┤
│ │
│ (RT1 is now invalid) │If a previously-invalidated refresh token is used (token replay attack), HelloJohn revokes the entire session.
Revoking tokens
Refresh tokens can be revoked immediately (server-side):
# Revoke a specific session
curl -X DELETE https://your-instance.com/v1/sessions/ses_01HABCDEF \
-H "Authorization: Bearer {access_token}"
# Revoke all sessions for the current user
curl -X DELETE https://your-instance.com/v1/sessions \
-H "Authorization: Bearer {access_token}"Access tokens cannot be revoked before expiry (they're stateless). To minimize the window of a leaked access token, reduce its lifetime. For immediate revocation requirements, use token introspection.
Next steps
Multi-Tenancy
HelloJohn is multi-tenant from the ground up. Learn the tenant model, how clients map to tenants, and how to build B2B SaaS with per-tenant isolation.
Database Schema
HelloJohn's database schema — global tables for the Control Plane and per-tenant tables for the Data Plane. Entity relationships, primary keys, and key indexes.