Sessions
What sessions are, how they work in HelloJohn, and how to manage them across devices.
Sessions
A session represents an authenticated period for a user. It is created when a user signs in and destroyed when they sign out or the session expires.
What Is a Session?
Every successful sign-in creates a session containing two tokens:
| Token | Lifetime | Purpose |
|---|---|---|
| Access token | 1 hour (configurable) | Authenticate API requests |
| Refresh token | 30 days (configurable) | Obtain new access tokens |
The access token is a signed JWT. The refresh token is an opaque token stored server-side by HelloJohn.
Session Object
{
"id": "ses_01HABCDEF999888",
"user_id": "usr_01HABCDEF123456",
"tenant_id": "tnt_01HABCDEF654321",
"status": "active",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...",
"ip_address": "203.0.113.42",
"country": "US",
"device_type": "desktop",
"created_at": "2024-01-15T10:30:00Z",
"last_active_at": "2024-01-20T08:15:00Z",
"expires_at": "2024-02-14T10:30:00Z"
}Session Lifecycle
Sign-in → Session created
↓
Access token expires (1h)
↓
Refresh token used → New access token issued
↓
Session expires (30d) or user signs out
↓
Session revokedMulti-Session Support
By default, users can have multiple active sessions simultaneously — one per device or browser. You can:
- List all sessions for a user
- Revoke a specific session (e.g., from a "Sign out this device" button)
- Revoke all sessions for a user (e.g., "Sign out of all devices")
Where Are Sessions Stored?
Sessions are stored server-side in HelloJohn's database. The client holds only the access token (in memory) and the refresh token (in a secure HttpOnly cookie or secure storage).
Never store the refresh token in localStorage or expose it to JavaScript — use HttpOnly cookies.
Verifying Sessions
Your backend should verify the access token on every request. You have two options:
Local Verification (Recommended)
Fetch the JWKS endpoint once and verify tokens locally. Fast, no network call:
import * as jose from "jose";
const JWKS = jose.createRemoteJWKSet(
new URL("https://api.hellojohn.dev/.well-known/jwks.json")
);
async function verifyToken(token: string) {
const { payload } = await jose.jwtVerify(token, JWKS, {
issuer: "https://api.hellojohn.dev",
audience: "your-tenant-id",
});
return payload;
}API Verification
Call POST /v1/sessions/verify with the token. Simpler but adds latency:
curl -X POST https://api.hellojohn.dev/v1/sessions/verify \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{"token": "eyJhbGciOiJFZERTQSJ9..."}'JWT Claims
| Claim | Type | Description |
|---|---|---|
sub | string | User ID |
session_id | string | Session ID |
tenant_id | string | Tenant ID |
org_id | string | Active organization ID (if any) |
role | string | User's role |
mfa_verified | boolean | Whether MFA was completed |
iat | number | Issued at (Unix timestamp) |
exp | number | Expires at (Unix timestamp) |
Configuration
Configure session behavior per tenant in the dashboard or via the Admin API:
| Setting | Default | Description |
|---|---|---|
session_duration | 30d | Refresh token lifetime |
access_token_ttl | 1h | Access token lifetime |
max_sessions_per_user | Unlimited | Revoke oldest on exceed |
idle_timeout | None | Revoke after period of inactivity |