Skip to content

Authentication

Operator console (apps/admin) uses email OTP + JWT. There is no password register/login. Tenant identity (users + accounts.password_hash) is separate from admin JWT roles.

  1. POST /api/v1/auth/login with { "email": "ops@example.com" }.
  2. Regular admins receive a one-time code (Workers Email / KV KV_OTP). Super admins listed in SUPER_ADMIN_EMAILS may receive an instant JWT.
  3. POST /api/v1/auth/otp/verify with { "email", "otp" } returns the JWT.
  4. Send it as a bearer token on /api/v1/admin/*:
Terminal window
curl https://<esim-api-host>/api/v1/admin/me \
-H "Authorization: Bearer <jwt>"

Signing key is Worker secret AUTH_SECRET (HS256). Do not register a separate JWT_SECRET.

Allowed admin emails: ALLOWED_ADMIN_EMAILS. Super admins: SUPER_ADMIN_EMAILS.

SurfaceRoles
Admin JWTsuper_admin | admin | viewer
Tenant (users.role)owner | admin | member | viewer

ProtectedRoute and requireAdminRoles enforce the admin JWT roles. Unauthenticated or under-privileged sessions redirect to /login.

X-Internal-Api-Key: ${INTERNAL_API_KEY} is still accepted on /admin/* for automation. Prefer JWT for the operator console.

Values live in GitHub Organization / Repository Secrets and Worker secrets. See the repo root .env.example (key names only).