Firebase Authentication in MCP-Cloud
This document provides detailed information about how MCP-Cloud implements Firebase Authentication, including setup, implementation details, security considerations, and integration patterns.
Overview
MCP-Cloud uses Firebase Authentication as its primary authentication mechanism for the web UI. Firebase Auth provides a secure, scalable, and feature-rich authentication system with minimal implementation effort.
Authentication Flow
The following diagram illustrates the authentication flow when using Firebase Authentication with MCP-Cloud:
User Frontend Firebase Auth MCP-Cloud Backend
| | | |
| 1. Login Request | | |
| --------------------> | | |
| | 2. Auth Request | |
| | ------------------------> | |
| | | |
| | 3. Auth Response | |
| | <------------------------ | |
| | | |
| | 4. Get ID Token | |
| | ------------------------> | |
| | | |
| | 5. ID Token | |
| | <------------------------ | |
| | | |
| | 6. API Request + Token | |
| | -----------------------------------------------------> |
| | | |
| | | 7. Verify Token |
| | | <--------------------------> |
| | | |
| | | 8. Create Session |
| | | |
| | 9. API Response + Cookie | |
| | <----------------------------------------------------- |
| | | |
| 10. UI Update | | |
| <-------------------- | | |
| | | |
Implementation Details
Frontend Implementation
MCP-Cloud uses the Firebase Web SDK for authentication. Here's a simplified example of the authentication context implementation:
import {
createContext,
useContext,
useState,
useEffect
} from 'react';
import {
getAuth,
onAuthStateChanged,
signInWithEmailAndPassword,
signInWithPopup,
GoogleAuthProvider,
GithubAuthProvider,
signOut as firebaseSignOut
} from 'firebase/auth';
import { app } from '@/lib/firebase';
// Create authentication context
const AuthContext = createContext<any>(null);
// Auth context provider component
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Initialize Firebase Auth
const auth = getAuth(app);
// Sync user state with Firebase
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (firebaseUser) => {
if (firebaseUser) {
// User is signed in
console.log('Auth state changed, user:', firebaseUser.uid);
try {
// Get ID token for backend API calls
const idToken = await firebaseUser.getIdToken();
// Sync user with backend
await syncUserWithBackend(firebaseUser, idToken);
setUser({
uid: firebaseUser.uid,
email: firebaseUser.email,
displayName: firebaseUser.displayName,
photoURL: firebaseUser.photoURL
});
} catch (err) {
console.error('Error processing authenticated user:', err);
setError(err.message);
}
} else {
// User is signed out
setUser(null);
}
setLoading(false);
});
// Cleanup subscription
return () => unsubscribe();
}, []);
// Sync user with backend
const syncUserWithBackend = async (firebaseUser, idToken) => {
console.log('Starting syncUserWithBackend for user:', firebaseUser.uid);
// Call backend API to create/validate session
try {
console.log('Got idToken, length:', idToken.length);
// User data to send to backend
const userData = {
firebaseUid: firebaseUser.uid,
email: firebaseUser.email,
displayName: firebaseUser.displayName || firebaseUser.email.split('@')[0],
photoURL: firebaseUser.photoURL
};
console.log('Sending user data to backend:', userData);
// Make API request to create session
console.log('Making API request to /api/auth/session');
const response = await fetch('/api/auth/session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${idToken}`
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`Backend sync failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
console.log('Backend sync successful:', data);
return data;
} catch (error) {
console.error('Error syncing user with backend:', error);
throw error;
}
};
// Email/password sign in
const signIn = async (email, password) => {
try {
setError(null);
return await signInWithEmailAndPassword(auth, email, password);
} catch (err) {
setError(err.message);
throw err;
}
};
// Google sign in
const signInWithGoogle = async () => {
try {
setError(null);
const provider = new GoogleAuthProvider();
return await signInWithPopup(auth, provider);
} catch (err) {
setError(err.message);
throw err;
}
};
// GitHub sign in
const signInWithGithub = async () => {
try {
setError(null);
const provider = new GithubAuthProvider();
return await signInWithPopup(auth, provider);
} catch (err) {
setError(err.message);
throw err;
}
};
// Sign out
const signOut = async () => {
try {
// Call backend to clear session
await fetch('/api/auth/signout', {
method: 'POST',
credentials: 'include'
});
// Sign out from Firebase
await firebaseSignOut(auth);
setUser(null);
} catch (err) {
setError(err.message);
throw err;
}
};
// Provide auth context to components
return (
<AuthContext.Provider
value={{
user,
loading,
error,
signIn,
signInWithGoogle,
signInWithGithub,
signOut
}}
>
{children}
</AuthContext.Provider>
);
}
// Hook for using auth context
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
Backend Implementation
On the backend, MCP-Cloud verifies Firebase ID tokens and creates server-side sessions:
import { Request, Response } from 'express';
import { auth } from 'firebase-admin';
import { storage } from '../storage';
class AuthController {
/**
* Create or validate a user session
*/
async createSession(req: Request, res: Response) {
try {
// Get the ID token from the Authorization header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Missing or invalid Authorization header' });
}
const idToken = authHeader.split('Bearer ')[1];
// Verify the ID token
const decodedToken = await auth().verifyIdToken(idToken);
const firebaseUid = decodedToken.uid;
// Get or create user in our database
let user = await storage.getUserByFirebaseUid(firebaseUid);
if (!user) {
// User doesn't exist in our database, create them
const { email, displayName, photoURL } = req.body;
user = await storage.createUser({
firebaseUid,
email,
displayName: displayName || email.split('@')[0],
photoURL
});
console.log('Created new user:', user);
} else {
// Update last login timestamp
await storage.updateUserLastLogin(user.id);
}
// Create session
const sessionId = await storage.createSession(user.id);
// Set session cookie
res.cookie('mcp_session', sessionId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
sameSite: 'strict'
});
console.log('Setting user session:', { userId: user.id, firebaseUid });
// Return user data (excluding sensitive fields)
console.log('Authentication successful, returning user data');
return res.json({
message: 'Authentication successful',
user: {
id: user.id,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
tier: user.tier,
createdAt: user.createdAt
}
});
} catch (error) {
console.error('Authentication error:', error);
return res.status(401).json({ message: 'Authentication failed', error: error.message });
}
}
/**
* Sign out and clear session
*/
async signOut(req: Request, res: Response) {
try {
// Clear session from database if exists
const sessionId = req.cookies.mcp_session;
if (sessionId) {
await storage.deleteSession(sessionId);
}
// Clear session cookie
res.clearCookie('mcp_session');
return res.json({ message: 'Successfully signed out' });
} catch (error) {
console.error('Sign out error:', error);
return res.status(500).json({ message: 'Sign out failed', error: error.message });
}
}
}
export default new AuthController();
Authentication Methods
MCP-Cloud supports the following authentication methods through Firebase:
Email/Password Authentication
Standard email and password authentication with:
- Password strength requirements
- Email verification
- Password reset functionality
Social Authentication
Integration with popular identity providers:
- GitHub
Multi-factor Authentication (MFA)
For enhanced security, MFA can be enabled with:
- SMS verification
- Authenticator apps (TOTP)
- Security keys (WebAuthn)
Security Considerations
Token Security
- ID tokens are short-lived (1 hour by default)
- Backend maintains server-side sessions for persistence
- Refresh tokens are handled securely by Firebase SDK
Custom Claims and User Roles
Firebase custom claims are used to implement role-based access control:
// Set admin role for a user
async function setAdminRole(uid) {
try {
await auth().setCustomUserClaims(uid, { admin: true });
console.log(`Successfully set admin role for user ${uid}`);
} catch (error) {
console.error(`Error setting admin role: ${error}`);
throw error;
}
}
// Set subscription tier for a user
async function setUserTier(uid, tier) {
try {
await auth().setCustomUserClaims(uid, { tier });
console.log(`Successfully set tier ${tier} for user ${uid}`);
} catch (error) {
console.error(`Error setting user tier: ${error}`);
throw error;
}
}
CSRF Protection
MCP-Cloud implements CSRF protection with:
- Secure, HTTP-only cookies for session management
- CSRF tokens for sensitive operations
- Strict same-site cookie policy
Firebase Configuration
Firebase Project Setup
To configure Firebase Authentication for MCP-Cloud:
- Create a Firebase project at firebase.google.com
- Enable Authentication in the Firebase console
- Configure the desired sign-in methods:
- Email/Password
- GitHub
- Other providers as needed
- Configure the authorized domains
- Set up Firebase Admin SDK for backend integration
Frontend Configuration
Configure the Firebase Web SDK in your frontend:
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID
};
export const app = initializeApp(firebaseConfig);
Backend Configuration
Configure Firebase Admin SDK in your backend:
// src/server/index.ts
import * as admin from 'firebase-admin';
import { applicationDefault } from 'firebase-admin/app';
// Initialize Firebase Admin SDK
admin.initializeApp({
credential: applicationDefault(),
projectId: process.env.FIREBASE_PROJECT_ID
});
Integration Patterns
Authentication with n8n
When integrating MCP-Cloud with n8n, you have two options for authentication:
Option 1: Using Firebase Authentication for n8n
Generate a Firebase token programmatically:
const getFirebaseToken = async (email, password) => { const response = await fetch( `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${FIREBASE_API_KEY}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password, returnSecureToken: true }) } ); const data = await response.json(); return data.idToken; };
Use the token with MCP-Cloud API:
const firebaseToken = await getFirebaseToken('[email protected]', 'password'); const mcpResponse = await fetch('https://api.mcp-cloud.ai/api/servers', { headers: { 'Authorization': `Bearer ${firebaseToken}` } });
Option 2: Using API Tokens (Recommended for Integration)
- Generate an API token in the MCP-Cloud dashboard
- Use the API token in n8n HTTP Request nodes:
Authorization: Bearer YOUR_API_TOKEN
Using Firebase Auth in Custom Clients
For custom clients that need to integrate with MCP-Cloud:
import { initializeApp } from 'firebase/app';
import { getAuth, signInWithEmailAndPassword, signOut } from 'firebase/auth';
// Initialize Firebase
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
// ...other config
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
// Sign in function
async function signIn(email, password) {
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// Get ID token for API calls
const idToken = await user.getIdToken();
return idToken;
} catch (error) {
console.error('Error signing in:', error);
throw error;
}
}
// API call function
async function callMcpCloudApi(endpoint, method = 'GET', data = null) {
// Get current user
const user = auth.currentUser;
if (!user) {
throw new Error('User not authenticated');
}
// Get fresh token
const idToken = await user.getIdToken(true);
// Make API call
const response = await fetch(`https://api.mcp-cloud.ai/api/${endpoint}`, {
method,
headers: {
'Authorization': `Bearer ${idToken}`,
'Content-Type': 'application/json'
},
body: data ? JSON.stringify(data) : undefined
});
return response.json();
}
// Example usage
async function example() {
try {
// Sign in
const token = await signIn('[email protected]', 'password');
// List servers
const servers = await callMcpCloudApi('servers');
console.log('Servers:', servers);
// Create new server
const newServer = await callMcpCloudApi('servers', 'POST', {
name: 'My New Server',
template: 'default',
// ... other configuration
});
console.log('New server:', newServer);
// Sign out
await signOut(auth);
} catch (error) {
console.error('Example failed:', error);
}
}
Troubleshooting
Common Issues
"Firebase ID token has expired"
This error occurs when using an expired token. Solutions:
- Get a fresh token using
user.getIdToken(true)
- Implement token refresh logic
- Use server-side sessions for longer persistence
- Get a fresh token using
"Firebase ID token has incorrect issuer"
This typically means your Firebase project configuration is incorrect. Verify:
- Project ID matches between frontend and backend
- Using the correct Firebase project credentials
"Firebase ID token has invalid signature"
This indicates a security issue with the token. Check:
- Proper initialization of Firebase Admin SDK
- Correct project credentials
- Potential token tampering
CORS issues with authentication
If experiencing CORS errors during authentication:
- Add your domain to the authorized domains in Firebase Console
- Configure CORS properly in your backend
- Ensure proper handling of preflight OPTIONS requests
Getting Help
If you encounter issues with Firebase Authentication:
- Check the Firebase Authentication documentation
- Review Firebase Auth error codes and messages
- Contact MCP-Cloud support for specific integration issues
- Check Firebase Status Dashboard for service disruptions