Home

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:

Social Authentication

Integration with popular identity providers:

Multi-factor Authentication (MFA)

For enhanced security, MFA can be enabled with:

Security Considerations

Token Security

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:

Firebase Configuration

Firebase Project Setup

To configure Firebase Authentication for MCP-Cloud:

  1. Create a Firebase project at firebase.google.com
  2. Enable Authentication in the Firebase console
  3. Configure the desired sign-in methods:
    • Email/Password
    • Google
    • GitHub
    • Other providers as needed
  4. Configure the authorized domains
  5. 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

  1. 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;
    };
    
  2. 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)

  1. Generate an API token in the MCP-Cloud dashboard
  2. 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

  1. "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
  2. "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
  3. "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
  4. 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:

  1. Check the Firebase Authentication documentation
  2. Review Firebase Auth error codes and messages
  3. Contact MCP-Cloud support for specific integration issues
  4. Check Firebase Status Dashboard for service disruptions