HelloJohn / docs
Sessions

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:

TokenLifetimePurpose
Access token1 hour (configurable)Authenticate API requests
Refresh token30 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 revoked

Multi-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:

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

ClaimTypeDescription
substringUser ID
session_idstringSession ID
tenant_idstringTenant ID
org_idstringActive organization ID (if any)
rolestringUser's role
mfa_verifiedbooleanWhether MFA was completed
iatnumberIssued at (Unix timestamp)
expnumberExpires at (Unix timestamp)

Configuration

Configure session behavior per tenant in the dashboard or via the Admin API:

SettingDefaultDescription
session_duration30dRefresh token lifetime
access_token_ttl1hAccess token lifetime
max_sessions_per_userUnlimitedRevoke oldest on exceed
idle_timeoutNoneRevoke after period of inactivity

On this page