Authentication & Sessions¶
Steam OpenID 2.0 via API Gateway — no separate user database needed.
Overview¶
The QS-Bridge panel uses a three-layer authentication architecture:
- API Gateway (
panel/api/) — Hono TypeScript server that handles Steam OpenID 2.0 verification and issues signed JWT session cookies - SpacetimeDB Module — stores
ModuleConfig.owner_identity(set duringinit). The gateway connects with the publisher's token, soctx.sender()matchesowner_identity - React Panel (
panel/ui/) — connects to STDB via WebSocket for read-only subscriptions. All admin mutations go through the gateway REST API
Authentication Flow¶
graph TD
A["User clicks 'Sign in with Steam'"] --> B["1. Browser → GET /auth/steam<br/>Gateway redirects to Steam OpenID login page"]
B --> C["2. User authenticates with Steam<br/>Steam redirects back to /auth/callback with assertion"]
C --> D["3. Gateway verifies Steam OpenID assertion<br/>Extracts Steam64 ID from claimed_id URL"]
D --> E{"4. Gateway looks up AdminRole table"}
E -->|"No row found"| F["Reject with 'Not an admin'"]
E -->|"Row exists"| G["5. Gateway calls panel_register_session reducer<br/>Creates PanelSession row in STDB"]
G --> H["6. Gateway issues JWT cookie<br/>qs-session: HttpOnly, 8-hour expiry, HS256 signed<br/>Payload: playerId, displayName, adminLevel, iat, exp"]
H --> I["7. Browser redirected to dashboard<br/>useAuth reads session via GET /auth/me"]
Session Management¶
| Property | Value |
|---|---|
| Cookie name | qs-session |
| HttpOnly | Yes (not accessible to JavaScript) |
| Expiry | 8 hours |
| Algorithm | HS256 (via jose library) |
| Secret | JWT_SECRET env var (64+ character random string) |
Session Lifecycle¶
- Login — Steam OpenID → JWT cookie →
PanelSessionrow created - Active —
GET /auth/mereturns session data on each page load - Mutations — admin actions sent via
POST /api/*with cookie attached - Logout —
POST /auth/logout→ cookie cleared →panel_end_sessionreducer called - Expiry — after 8 hours,
GET /auth/mereturns 401 → redirect to login
Admin Role Levels¶
| Level | Value | Permissions |
|---|---|---|
| Moderator | 0 | Ban, kick, whitelist management, audit log view |
| Admin | 1 | All Moderator permissions + server config, role management |
| Owner | 2 | All Admin permissions + SQL console, table browser, developer tools |
First-Time Bootstrap¶
The server starts with zero admin roles. Bootstrap the first Owner via CLI:
# Using the bootstrap script (recommended):
./panel/bootstrap-admin.sh 76561198XXXXXXXXX YourName
# Or manually:
spacetime sql qs-bridge \
"INSERT INTO admin_role (player_id, level, granted_by, granted_at) \
VALUES ('Steam:76561198XXXXXXXXX', 2, 'bootstrap', 0)"
You need
level = 2(Owner) for the first account so you can grant roles to others from the panel.
Inviting Other Admins¶
Once logged in as Owner:
- From the panel — Admin Controls → Roles tab → Grant Admin section
- From CLI:
The new admin can then sign in via Steam — no tokens or secrets needed.
React Hooks¶
useAuth()¶
Context consumer for authentication state:
const { user, isLoading, isAuthenticated, logout } = useAuth();
// user: { playerId, displayName, adminLevel } | null
useApi()¶
Fetch wrapper for gateway API calls (attaches JWT cookie automatically):
Security Properties¶
| Property | Implementation |
|---|---|
| No client-side secrets | JWT is HttpOnly — JavaScript cannot read it |
| No STDB credentials in browser | WebSocket is read-only subscriptions only |
| Gateway-only mutations | All admin reducers verify is_gateway() |
| No password database | Steam authenticates users; STDB stores only Steam IDs |
| Audit trail | Every admin action logged in PanelAuditLog |
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
| "Sign in with Steam" does nothing | Gateway not running | Start panel/api with npm run dev |
| Steam login redirects but panel shows error | GATEWAY_URL mismatch |
Set GATEWAY_URL to your panel's public URL |
| "Not an admin" after Steam login | No admin_role row |
Bootstrap via ./panel/bootstrap-admin.sh |
| Dashboard shows but no live data | STDB connection failed | Check VITE_STDB_HOST env var |
| Session expired | 8-hour JWT expiry | Sign in again |
Related Pages¶
- API Gateway — REST API reference
- AdminRole Table — role data structure
- PanelSession Table — session data structure