Auth & Storage
Roll your own. Watchtower handles multiplayer — you handle identity and persistence.
What Watchtower Provides
Watchtower gives you real-time multiplayer infrastructure:
- Rooms — create, join, leave
- Messaging — broadcast and direct messages
- Presence — who's online, join/leave events
That's it. No accounts, no databases, no persistence. When a player disconnects, they're gone.
What You Bring
For identity and storage, use your own stack:
- Auth — user accounts, login, sessions
- Database — saves, inventories, leaderboards, anything that persists
Your game is a web app. You can use any auth provider and any database that works on the web.
Recommended: Supabase
The easiest path is Supabase. Free tier, 5-minute setup, handles both auth and database.
1. Set up Supabase
npm install @supabase/supabase-js// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)2. Auth: Login Flow
import { supabase } from './lib/supabase'
// Sign up
await supabase.auth.signUp({ email, password })
// Login
await supabase.auth.signInWithPassword({ email, password })
// OAuth (Google, Discord, GitHub, etc.)
await supabase.auth.signInWithOAuth({ provider: 'discord' })
// Get current user
const { data: { user } } = await supabase.auth.getUser()3. Connect to Watchtower with User Identity
import { connect } from '@watchtower-sdk/core'
import { supabase } from './lib/supabase'
// Get authenticated user
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
window.location.href = '/login'
return
}
// Connect with their real identity
const room = await connect('game-room', {
playerId: user.id,
playerName: user.user_metadata?.name || user.email?.split('@')[0]
})4. Storage: Save to Your Database
// Save player progress
async function saveProgress(userId: string, level: number, score: number) {
await supabase
.from('player_progress')
.upsert({ user_id: userId, level, score })
}
// Load player progress
async function loadProgress(userId: string) {
const { data } = await supabase
.from('player_progress')
.select('*')
.eq('user_id', userId)
.single()
return data
}
// Leaderboard
async function getLeaderboard() {
const { data } = await supabase
.from('player_progress')
.select('user_id, score')
.order('score', { ascending: false })
.limit(10)
return data
}Other Options
Supabase not your thing? Any of these work:
| Service | Auth | Database |
|---|---|---|
| Firebase | ✓ | ✓ |
| Clerk | ✓ | — |
| Auth0 | ✓ | — |
| PlanetScale | — | ✓ |
| Turso | — | ✓ |
Guest / Anonymous Players
Not every game needs accounts. For casual games, generate a local ID:
function getGuestId() {
let id = localStorage.getItem('guestId')
if (!id) {
id = 'guest_' + crypto.randomUUID()
localStorage.setItem('guestId', id)
}
return id
}
const room = await connect('casual-game', {
playerId: getGuestId(),
playerName: 'Guest'
})This gives players a consistent identity across sessions on the same device, without requiring signup. Just remember: no backend means no persistence across devices.
The Pattern
// 1. User logs in (your auth)
const user = await yourAuth.getCurrentUser()
// 2. Connect to Watchtower with their identity
const room = await connect('room', {
playerId: user.id,
playerName: user.name
})
// 3. Real-time game state → Watchtower messages
room.broadcast({ x, y, action })
// 4. Persistent data → your database
await yourDB.saveProgress(user.id, { level, score })Watchtower handles the real-time multiplayer. Your stack handles everything that persists.