Skip to content

Authentication

Orbital uses passwordless authentication with WebAuthn/passkeys. This page gives a high‑level view of the flow and shows how bootstrap and the CLI fit in. For full browser implementation details, see Passkey Authentication Flow and Passkey Setup Guide.

sequenceDiagram
participant User
participant App
participant API
participant Device
User->>App: Follow invitation link
App->>API: POST /auth/passkey/begin?invite={code}
API->>App: Challenge + Session ID
App->>Device: Create passkey
Device->>User: Biometric prompt
User->>Device: Authenticate
Device->>App: Signed credential
App->>API: POST /auth/passkey/complete
API->>App: Success + Session token
sequenceDiagram
participant User
participant App
participant API
participant Device
User->>App: Click "Sign in"
App->>API: POST /auth/passkey/begin
API->>App: Challenge + Session ID
App->>Device: Sign challenge
Device->>User: Biometric prompt
User->>Device: Authenticate
Device->>App: Signed response
App->>API: POST /auth/passkey/complete
API->>App: Success + Auth token
import { startRegistration } from '@simplewebauthn/browser';
// Step 1: Begin registration
const invitationCode = 'abc123xyz';
// Begin registration with invitation
const beginResponse = await fetch(`/api/auth/passkey/begin?invite=${invitationCode}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const { publicKey, sessionId } = await beginResponse.json();
// Step 2: Create credential with device
const credential = await startRegistration(publicKey);
// Step 3: Complete registration
const completeResponse = await fetch('/api/auth/passkey/complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'SessionID': sessionId,
},
body: JSON.stringify(credential),
});
const { token } = await completeResponse.json();
// Store token for authenticated requests
import { startAuthentication } from '@simplewebauthn/browser';
// Step 1: Begin login
const beginResponse = await fetch('/api/auth/passkey/begin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
});
const { publicKey, sessionId } = await beginResponse.json();
// Step 2: Sign challenge with device
const credential = await startAuthentication(publicKey);
// Step 3: Complete login
const completeResponse = await fetch('/api/auth/passkey/complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'SessionID': sessionId,
},
body: JSON.stringify(credential),
});
// Token is set as httpOnly cookie automatically

When deploying Orbital for the first time with no admin accounts, the system enters bootstrap mode to allow initial admin registration.

  1. Server starts with no admins → creates bootstrap invitation automatically
  2. Bootstrap invitation logged to console with token
  3. Invitation endpoint accessible without authentication (only in bootstrap mode)
  4. First admin registers using invitation token → bootstrap mode disabled

The recommended way to bootstrap is via the CLI:

Terminal window
# 1. List invitations (no authentication required in bootstrap mode)
orbital invite list
# Output:
# ● abc123xyz
# Status: active
# Expires: 2024-12-21T10:30:00Z
# 2. Register with invitation token
orbital auth register abc123xyz
# Browser opens → complete passkey registration → first admin created
# 3. Authenticate the CLI
orbital auth login

For programmatic access or CI/CD workflows:

Terminal window
# Get bootstrap invitation (requires bootstrap secret header)
curl -H "X-Bootstrap-Secret: orbital-bootstrap-d4c8e7b2-9f1a-4b3e-8c6d-5a7e9f2b1c3d" \
https://your-api.com/api/auth/invitations
# Response:
# [
# {
# "token": "abc123xyz",
# "createdBy": 0,
# "status": "active",
# "expiresAt": "2024-12-21T10:30:00Z"
# }
# ]

Then open the registration URL:

https://your-api.com/api/auth/signin?invite=abc123xyz

The bootstrap secret header prevents accidental discovery while keeping access simple:

  • Header: X-Bootstrap-Secret
  • Value: orbital-bootstrap-d4c8e7b2-9f1a-4b3e-8c6d-5a7e9f2b1c3d (well-known, documented)
  • Purpose: Light security gate for empty systems
  • Works with: CLI (automatic), curl, Postman, scripts

Bootstrap mode is safe because:

  • Empty system - No data exists to protect when adminCount = 0
  • Invitation ≠ access - Still requires passkey registration (phishing-resistant)
  • Self-disabling - Endpoint returns 401 after first admin exists
  • Time-limited - Bootstrap invitations expire in 7 days
  • Audit trail - All bootstrap access attempts are logged
  • Bootstrap secret - Prevents accidental/automated discovery

Development/Staging (behind VPN):

Terminal window
# No special configuration needed
docker compose up -d
./orbital serve

Production (public internet):

Terminal window
# Same process, bootstrap invitation logged
./orbital serve
# Admin uses CLI from secure workstation
orbital invite list
orbital auth register <token>
orbital auth login

For high-security environments, monitor logs for bootstrap invitation generation and immediately use it to create the first admin.