HelloJohn / docs
Architecture

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 TokenRefresh Token
FormatJWT (EdDSA-signed)Opaque string
Default lifetime15 minutes30 days
StorageMemory (in SDKs)Secure storage / HttpOnly cookie
VerifiedLocally — no API call neededServer-side only
RevocableNot immediately (stateless)Immediately (stored in DB)
Sent toYour backend (Authorization header)HelloJohn only (refresh endpoint)

JWT structure

HelloJohn access tokens are standard JWTs. You can inspect any token at jwt.io.

{
  "alg": "EdDSA",
  "kid": "key_01HABCDEF123456",
  "typ": "JWT"
}
FieldValueDescription
algEdDSAEd25519 signing algorithm
kidkey_01HABCDEF...Key ID — matches a key in JWKS
typJWTStandard 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
}
ClaimTypeDescription
substringUser ID — use as the primary user identifier in your backend
tenant_idstringTenant this user belongs to
client_idstringOAuth2 client that issued the token
emailstringUser's email address
namestringUser's display name
session_idstringSession ID — use for session revocation
rolestringUser's global role (super_admin, admin, member)
org_idstringActive organization ID (null if not in org context)
mfa_verifiedbooleanWhether the user passed MFA in this session
customobjectCustom claims set via tenant configuration
iatnumberIssued at (Unix timestamp)
expnumberExpiry (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 size32 bytes2048+ bits
Signature size64 bytes256 bytes
Verification speed~5× fasterBaseline
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.json

Response:

{
  "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:

  1. The new key is added to JWKS (alongside the old key)
  2. New tokens are issued with the new key (kid in header)
  3. Tokens issued with the old key remain verifiable until expiry
  4. 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:

.env
# Access token lifetime (default: 15m)
HELLOJOHN_ACCESS_TOKEN_TTL=15m

# Refresh token lifetime (default: 30d)
HELLOJOHN_REFRESH_TOKEN_TTL=30d

Or 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

On this page