Firebase - pacificnm/wiki-ai GitHub Wiki
๐ฅ Firebase Configuration
This document describes the Firebase Admin SDK configuration and authentication implementation in the wiki-ai application.
๐ Configuration Files
/server/config/firebase.js
The main Firebase configuration file that:
- โ Initializes Firebase Admin SDK
- โ Provides helper functions for Auth, Firestore, and Storage
- โ Handles token verification
- โ Manages user operations
/server/middleware/auth.js
Authentication middleware that:
- โ Verifies Firebase ID tokens
- โ Extracts user information
- โ Provides role-based access control
- โ Supports optional authentication
๐ Environment Variables
Add these to your .env
file:
# Firebase Admin SDK Configuration
FIREBASE_PROJECT_ID=your-firebase-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----\n"
# Optional Firebase services
FIREBASE_DATABASE_URL=https://your-project-id-default-rtdb.firebaseio.com
FIREBASE_STORAGE_BUCKET=your-project-id.appspot.com
๐ Getting Firebase Credentials
- Go to Firebase Console โ Project Settings โ Service Accounts
- Generate new private key โ Download JSON file
- Extract values from JSON:
{ "project_id": "your-firebase-project-id", "client_email": "[email protected]", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n" }
๐ Initialization
The Firebase Admin SDK is initialized in server/index.js
:
import { initializeFirebase } from './config/firebase.js';
async function startServer() {
try {
// Initialize Firebase before starting routes
await initializeFirebase();
// Continue with server setup...
} catch (error) {
logger.error('Server startup failed', { error: error.message });
process.exit(1);
}
}
๐ง Available Functions
Core Firebase Functions
import {
initializeFirebase,
getAuth,
getFirestore,
getStorage
} from './config/firebase.js';
// Initialize (called once at startup)
await initializeFirebase();
// Get service instances
const auth = getAuth();
const db = getFirestore();
const storage = getStorage();
Authentication Functions
import {
verifyIdToken,
getUserById,
setCustomClaims
} from './config/firebase.js';
// Verify a client's ID token
const decodedToken = await verifyIdToken(clientToken);
// Get user data by UID
const user = await getUserById('user-uid-here');
// Set custom claims (for roles)
await setCustomClaims('user-uid', { role: 'admin' });
๐ก๏ธ Authentication Middleware
Required Authentication
import { authenticateToken, requireUser } from '../middleware/auth.js';
// Protect routes that require authentication
router.get('/profile',
authenticateToken,
requireUser,
(req, res) => {
res.json({
message: `Hello ${req.user.email}`,
uid: req.user.uid,
role: req.user.role
});
}
);
Admin-Only Routes
import { authenticateToken, requireAdmin } from '../middleware/auth.js';
// Admin-only routes
router.delete('/admin/users/:id',
authenticateToken,
requireAdmin,
adminController.deleteUser
);
Optional Authentication
import { optionalAuth } from '../middleware/auth.js';
// Works with or without authentication
router.get('/public-content',
optionalAuth,
(req, res) => {
if (req.user) {
res.json({ message: `Hello ${req.user.email}`, isAuthenticated: true });
} else {
res.json({ message: 'Hello anonymous user', isAuthenticated: false });
}
}
);
๐ค User Object Structure
When authenticated, req.user
contains:
{
uid: "firebase-user-uid",
email: "[email protected]",
emailVerified: true,
displayName: "John Doe",
photoURL: "https://...",
role: "admin", // From custom claims
customClaims: { /* Full decoded token */ },
firebaseUser: { /* Firebase UserRecord */ }
}
๐ Role-Based Access Control
Setting User Roles
import { setCustomClaims } from './config/firebase.js';
// Make user an admin
await setCustomClaims('user-uid', { role: 'admin' });
// Make user a regular user
await setCustomClaims('user-uid', { role: 'user' });
// Add multiple claims
await setCustomClaims('user-uid', {
role: 'admin',
permissions: ['read', 'write', 'delete']
});
Using Roles in Routes
// Admin-only endpoint
router.post('/admin/categories',
authenticateToken,
requireAdmin,
categoryController.createCategory
);
// User endpoint (any authenticated user)
router.get('/my-documents',
authenticateToken,
requireUser,
documentController.getUserDocuments
);
// Role-specific logic
router.get('/documents/:id',
authenticateToken,
requireUser,
(req, res, next) => {
if (req.user.role === 'admin') {
// Admins can see any document
next();
} else {
// Users can only see their own documents
// Add ownership check here
next();
}
},
documentController.getDocument
);
๐งช Testing Authentication
Valid Request Example
curl -H "Authorization: Bearer YOUR_FIREBASE_ID_TOKEN" \
http://localhost:5000/api/profile
Expected Response
{
"message": "Hello [email protected]",
"uid": "firebase-user-uid",
"role": "user"
}
Error Responses
// No token
{
"error": "Authorization header required",
"message": "Please provide Authorization header with Bearer token"
}
// Invalid token
{
"error": "Authentication failed",
"message": "Invalid or expired token"
}
// Insufficient permissions
{
"error": "Admin access required",
"message": "You do not have administrator privileges"
}
๐จ Error Handling
Firebase operations include comprehensive error handling:
// Initialization errors
try {
await initializeFirebase();
} catch (error) {
logger.error('Firebase initialization failed', { error: error.message });
process.exit(1);
}
// Token verification errors
try {
const decodedToken = await verifyIdToken(token);
} catch (error) {
// Automatically logged and handled
throw new Error('Invalid or expired token');
}
๐ง Advanced Usage
Custom Token Validation
import { verifyIdToken } from './config/firebase.js';
// Custom middleware for API keys + Firebase
export async function hybridAuth(req, res, next) {
const apiKey = req.headers['x-api-key'];
const authHeader = req.headers.authorization;
if (apiKey && isValidApiKey(apiKey)) {
req.user = { type: 'api', key: apiKey };
return next();
}
if (authHeader && authHeader.startsWith('Bearer ')) {
try {
const token = authHeader.substring(7);
const decoded = await verifyIdToken(token);
req.user = { type: 'firebase', ...decoded };
return next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
return res.status(401).json({ error: 'Authentication required' });
}
Firestore Integration
import { getFirestore } from './config/firebase.js';
export async function syncUserToFirestore(userRecord) {
const db = getFirestore();
await db.collection('users').doc(userRecord.uid).set({
email: userRecord.email,
displayName: userRecord.displayName,
emailVerified: userRecord.emailVerified,
createdAt: new Date(),
lastLoginAt: new Date()
}, { merge: true });
}
๐ Security Best Practices
- Environment Variables: Never commit Firebase private keys to version control
- Token Validation: Always verify tokens on the server side
- Custom Claims: Use for role-based access control
- HTTPS Only: Use HTTPS in production for token security
- Token Expiry: Firebase ID tokens expire after 1 hour
- Error Handling: Don't expose sensitive error details to clients