A secure, emulator-first Firebase backend starter for Unity projects with authentication, Cloud Functions, and Firestore.
- What this repository is
- Why it exists
- MVP features
- Architecture at a glance
- Repository structure
- Prerequisites
- Quickstart (local development)
- Example backend API: submitScore
- Firestore security model
- Unity client integration (overview)
- Testing
- Verification
- Assumptions made for MVP
- Sensitive information review
- Deployment
- What this template does not include
- When to use this repository
- License
This repository is a Firebase backend starter for Unity projects. It provides a secure, emulator-first setup using Firebase Auth, Cloud Functions, and Firestore, intended to be cloned and extended. It is not a full game backend and does not include gameplay logic or real-time servers.
Unity developers often need a lightweight backend for authentication, validation, and persistence, but setting up Firebase correctly is time-consuming and error-prone.
This repository exists to:
• Provide a ready-to-use Firebase backend for Unity
• Demonstrate secure server-side validation patterns
• Prevent direct client writes to sensitive data
• Enable fast local development using Firebase emulators
Target users include indie Unity developers, game jam teams, and small projects that need backend validation without running custom servers.
• Firebase Auth integration (anonymous sign-in supported)
• HTTP Cloud Functions with server-side token validation
• Firestore schema and security rules aligned with backend logic
• Firebase Emulator Suite for local development
• Seeded emulator data for fast testing
• Example backend endpoint: submitScore
• Minimal automated tests against emulators
Unity Client
→ Firebase Auth (ID token)
→ HTTPS Cloud Function
→ Firestore (server-validated writes only)
Clients never write directly to protected collections.
graph TB
subgraph "Unity Client"
UC[Unity Game]
FA[Firebase Auth SDK]
end
subgraph "Firebase Backend"
AUTH[Firebase Auth Service]
CF[Cloud Functions]
FS[Firestore Database]
end
subgraph "Development Environment"
EMU[Firebase Emulators]
EMUAUTH[Emulated Auth]
EMUCF[Emulated Functions]
EMUFS[Emulated Firestore]
SEED[Seeded Test Data]
end
%% Production Flow
UC -->|1. Sign In| FA
FA -->|2. Request Token| AUTH
AUTH -->|3. ID Token| FA
FA -->|4. Return Token| UC
UC -->|5. HTTP Request + Bearer Token| CF
CF -->|6. Verify Token| AUTH
CF -->|7. Validate & Process| CF
CF -->|8. Server Write| FS
FS -->|9. Success Response| CF
CF -->|10. JSON Response| UC
UC -.->|11. Read Only| FS
%% Development Flow
EMU -.->|Contains| EMUAUTH
EMU -.->|Contains| EMUCF
EMU -.->|Contains| EMUFS
SEED -.->|Imports to| EMUFS
style UC fill:#e1f5ff
style CF fill:#fff4e1
style FS fill:#e8f5e9
style AUTH fill:#fce4ec
style EMU fill:#f3e5f5
style SEED fill:#fff9c4
sequenceDiagram
participant Unity as Unity Client
participant Auth as Firebase Auth
participant CF as Cloud Function
participant FS as Firestore
Note over Unity,FS: Score Submission Flow
Unity->>Auth: 1. Sign in (anonymous/email)
Auth-->>Unity: 2. User credentials
Unity->>Auth: 3. Get ID Token
Auth-->>Unity: 4. ID Token (JWT)
Unity->>CF: 5. POST /submitScore<br/>{score: 1234}<br/>Header: Bearer {token}
CF->>Auth: 6. Verify ID Token
Auth-->>CF: 7. Decoded token (uid)
alt Valid Token & Score
CF->>CF: 8. Validate score<br/>(positive integer, max limit)
CF->>FS: 9. Write /users/{uid}
CF->>FS: 10. Write /leaderboard/{uid}
FS-->>CF: 11. Write confirmed
CF-->>Unity: 12. Success response<br/>{success: true, score: 1234}
else Invalid Request
CF-->>Unity: Error response<br/>(401/400/500)
end
Note over Unity,FS: Read Operations
Unity->>FS: Read own user data<br/>(authenticated)
FS-->>Unity: User document
Unity->>FS: Read leaderboard<br/>(authenticated, read-only)
FS-->>Unity: Leaderboard entries
unity-firebase-backend-starter/
│
├── functions/ # Cloud Functions source code
│ ├── src/
│ │ └── index.ts # Main functions (submitScore endpoint)
│ ├── test/
│ │ └── submitScore.test.ts # Automated tests
│ ├── lib/ # Compiled JavaScript (generated)
│ ├── package.json # Node.js dependencies and scripts
│ ├── tsconfig.json # TypeScript configuration
│ ├── tsconfig.dev.json # TypeScript dev configuration
│ └── .eslintrc.js # ESLint configuration
│
├── emulator-data/ # Seeded data for local emulators
│
├── firestore.rules # Firestore security rules
├── firestore.indexes.json # Firestore database indexes
├── firebase.json # Firebase project configuration
├── .firebaserc # Firebase project aliases
├── LICENSE # MIT License
├── DISCLOSURE.md # AI-assisted development disclosure
└── README.md # This file
| File | Purpose |
|---|---|
functions/src/index.ts |
Cloud Functions implementation (submitScore) |
functions/test/submitScore.test.ts |
Automated tests for backend validation |
firestore.rules |
Security rules preventing direct client writes |
firebase.json |
Emulator configuration and project settings |
emulator-data/ |
Pre-seeded test data for quick local development |
• Node.js 16
• Firebase CLI
• Unity 2021 or newer
Install Firebase CLI if needed:
npm install -g firebase-tools
Install backend dependencies:
npm --prefix functions install
Build Cloud Functions:
npm --prefix functions run build
Start Firebase emulators:
npm --prefix functions run serve
This starts local emulators for Auth, Firestore, and Cloud Functions.
POST /submitScore
• Firebase ID token required
• Token validated server-side
{
"score": 1234
}• User must be authenticated
• Score must be a positive integer
• Maximum score enforced server-side
• Optional cooldown between submissions
/users/{uid}
/leaderboard/{uid}
All writes are performed exclusively by Cloud Functions.
• Users can read their own user document
• Users cannot write leaderboard entries directly
• Leaderboard data is read-only from the client
• Server writes bypass client restrictions
See firestore.rules for exact definitions.
graph LR
subgraph "Client Operations"
C["Unity Client<br/>Authenticated User"]
end
subgraph "Firestore Collections"
U["/users/UID"]
L["/leaderboard/UID"]
end
subgraph "Server Operations"
CF["Cloud Functions<br/>Admin SDK"]
end
%% Client permissions
C -->|✅ READ own document| U
C -.->|❌ WRITE denied| U
C -->|✅ READ all entries| L
C -.->|❌ WRITE denied| L
%% Server permissions
CF -->|✅ WRITE validated data| U
CF -->|✅ WRITE validated data| L
style C fill:#e1f5ff
style U fill:#e8f5e9
style L fill:#e8f5e9
style CF fill:#fff4e1
Users Collection (/users/{uid})
- Read: Allowed only if authenticated AND requesting own document (
request.auth.uid == userId) - Write: Denied for all clients (only Cloud Functions can write)
Leaderboard Collection (/leaderboard/{uid})
- Read: Allowed for any authenticated user
- Write: Denied for all clients (only Cloud Functions can write)
Default Rule
- All other collections: Read and write denied
Typical Unity flow:
• Initialize Firebase
• Sign in anonymously
• Fetch Firebase ID token
• Call HTTPS endpoint with Authorization header
• Handle JSON response
flowchart TD
Start([Unity Game Starts]) --> Init[Initialize Firebase SDK]
Init --> SignIn[Sign In User<br/>Anonymous or Email]
SignIn --> GetToken[Get ID Token from<br/>FirebaseAuth]
GetToken --> PrepReq[Prepare HTTP Request<br/>POST /submitScore]
PrepReq --> AddAuth[Add Authorization Header<br/>Bearer TOKEN]
AddAuth --> AddBody[Add JSON Body<br/>score: value]
AddBody --> Send[Send UnityWebRequest]
Send --> WaitResp{Wait for Response}
WaitResp -->|Success 200| ParseSuccess[Parse JSON Response]
WaitResp -->|Error 4xx/5xx| ParseError[Parse Error Response]
ParseSuccess --> UpdateUI[Update UI<br/>Show Success]
ParseError --> ShowError[Show Error Message]
UpdateUI --> End([Continue Game])
ShowError --> End
style Start fill:#e1f5ff
style End fill:#e1f5ff
style ParseSuccess fill:#e8f5e9
style ParseError fill:#ffebee
style Send fill:#fff4e1
// Pseudo-flow:
FirebaseAuth → GetIdToken
UnityWebRequest → submitScore
Parse responseA minimal Unity C# example can be added separately or embedded later.
Run tests:
npm --prefix functions run test
Included tests validate:
• Unauthenticated requests are rejected
• Request validation (method, headers, body)
• Score validation (type, range, integer requirement)
graph TB
subgraph "Test Suite"
T[Mocha Test Runner]
TC1[Test: Unauthorized Request]
TC2[Test: Invalid Method]
TC3[Test: Invalid Score Type]
TC4[Test: Negative Score]
TC5[Test: Score Too High]
TC6[Test: Valid Score]
end
subgraph "Firebase Test Environment"
FT[Firebase Functions Test]
EA[Emulated Auth]
EF[Emulated Functions]
EFS[Emulated Firestore]
end
T --> TC1
T --> TC2
T --> TC3
T --> TC4
T --> TC5
T --> TC6
TC1 --> FT
TC2 --> FT
TC3 --> FT
TC4 --> FT
TC5 --> FT
TC6 --> FT
FT --> EA
FT --> EF
FT --> EFS
EF -.->|Validates using| EA
EF -.->|Writes to| EFS
style T fill:#e1f5ff
style FT fill:#fff4e1
style EA fill:#fce4ec
style EF fill:#fff4e1
style EFS fill:#e8f5e9
This section describes how to verify the MVP is working correctly.
• Java 21 or higher (required for Firebase Emulators) • Node.js 16 or higher • Firebase CLI installed globally
# Install dependencies
npm --prefix functions install
# Build the functions
npm --prefix functions run build
# Run tests
npm --prefix functions run testExpected output: All tests should pass, validating:
- Authentication requirements are enforced
- Invalid requests are rejected with appropriate error codes
- Score validation works correctly (positive integers only, maximum enforced)
# Start emulators (requires Java 21+)
firebase emulators:start --import=./emulator-dataThe emulators will start on:
- Auth:
http://localhost:9099 - Firestore:
http://localhost:8080 - Functions:
http://localhost:5001 - Emulator UI:
http://localhost:4000
You can test the submitScore endpoint manually using curl:
# This should return 401 Unauthorized (no token)
curl -X POST http://localhost:5001/fir-unitygamebussbackend/us-central1/submitScore \
-H "Content-Type: application/json" \
-d '{"score": 1234}'The following assumptions were made during MVP implementation:
-
Maximum Score: Set to 999999 as a reasonable upper bound. Can be configured in
functions/src/index.ts. -
Cooldown: The README mentioned "optional cooldown between submissions" but this was not implemented as it was marked optional. The MVP allows unlimited submissions from authenticated users.
-
Seeded Emulator Data: The existing emulator data is preserved and imported on emulator start, but no additional seed data was created for the MVP.
-
Error Responses: All error responses follow a consistent JSON format with
errorandmessagefields for client-side error handling. -
CORS: CORS headers are set to allow all origins (
*) for development convenience. In production, this should be restricted to specific domains. -
Anonymous Auth: Firebase Auth with anonymous sign-in is supported but not explicitly implemented or tested in the backend (server accepts any valid Firebase ID token).
Status: ✅ Complete
Files Reviewed:
- Source code (
functions/src/) - Configuration files (
firebase.json,firestore.rules,firestore.indexes.json,.firebaserc) - Environment files (
.env- none present) - Package files (
package.json,package-lock.json) - Test files (
functions/test/) - Emulator data (
emulator-data/)
Findings:
- ✅ No API keys or secrets found in source code
- ✅ No credentials in configuration files
- ✅ Project ID in
.firebasercis a Firebase project identifier (public, not sensitive) - ✅ No environment variables with secrets
- ✅
.gitignoreproperly configured to exclude sensitive files (.env, Firebase cache) - ✅ Emulator data contains only test/seed data, no production credentials
Recommendations:
- Before production deployment, create a
.envfile for sensitive configuration (do not commit it) - Store production API keys and service account credentials outside of version control
- Use Firebase project-specific service accounts for production deployments
- Rotate any credentials if you suspect they may have been exposed
Select Firebase project:
firebase use <project-id>
Deploy backend and rules:
firebase deploy
Always verify behavior locally using emulators before deploying.
flowchart TD
Start([Ready to Deploy]) --> Local[Test Locally with Emulators]
Local --> AllPass{All Tests Pass?}
AllPass -->|No| Fix[Fix Issues]
Fix --> Local
AllPass -->|Yes| Build[Build Functions<br/>npm run build]
Build --> SelectProj[Select Firebase Project<br/>firebase use project-id]
SelectProj --> Deploy[Deploy to Firebase<br/>firebase deploy]
Deploy --> DeployWhat{What to Deploy?}
DeployWhat -->|Functions Only| DF[firebase deploy --only functions]
DeployWhat -->|Rules Only| DR[firebase deploy --only firestore:rules]
DeployWhat -->|Everything| DA[firebase deploy]
DF --> Verify
DR --> Verify
DA --> Verify
Verify[Verify in Production] --> Success{Working?}
Success -->|Yes| Done([Deployment Complete])
Success -->|No| Rollback[Check Logs & Rollback]
Rollback --> Fix
style Start fill:#e1f5ff
style Done fill:#e8f5e9
style AllPass fill:#fff9c4
style Success fill:#fff9c4
style Rollback fill:#ffebee
style Deploy fill:#fff4e1
• Multiplayer matchmaking
• Real-time authoritative game servers
• Advanced anti-cheat systems
• Payments or receipt validation
These are intentionally out of scope.
Use this template if you need:
• Server-validated scores or progression
• Secure storage for Unity games
• Fast local iteration with emulators
• A lightweight Firebase backend
Do not use it if you need:
• MMO-scale infrastructure
• Low-latency real-time simulation
MIT. See LICENSE file for details.
See also DISCLOSURE.md for important information about AI-assisted development and security responsibilities.