skip to Main Content

I don’t have any software experience, I just started using AI to build something for myself in late November. I’m working with a React web app and I’m trying to deploy it in production. I’m trying to run my Firebase functions, but I’m getting this error:

Failed to load resource: the server responded with a status of 401 ()
LGChat.js:78 Full error object: FirebaseError: The function must be called while authenticated.

It is telling me I need to be authenticated to do it. however, I am fully authenticated. I’ve logged in, and Firebase storage is pulling the documents associated with the account. so I know that other Firebase services are working. I’ve deleted the account and remade it, so I know the auth isn’t broken. and the way callable functions work from my understanding is Firebase does the token work on their end. so nothing I add to my code has influenced the token being sent.

Every time I try to use the function, the token is sent, and then Firebase denies it. I can’t imagine that the token is incorrect. It is the one Firebase is generating and pulling itself from its own auth.

The LLMs have suggested mostly environmental issues. I’ve checked every one they’ve suggested. I’ve also confirmed that I am fully signed into the Google Cloud SDK.

Does anyone have any experience that could point me in a new direction to solve this? it seems like such a dumb problem lol.

The error message is being called from my functions/index.js and in a file called LGChat. Please let me know what else I can send to be helpful. I’m new to this so feel free to assume I don’t know what you are talking about. Also, sorry if I’ve structured this Stack Overflow post incorrectly.

I’ve carefully gone through all of the potential environmental issues.

I confirmed I’m logged into Google Cloud.

I’ve gone through the callables documentation to confirm everything is set up correctly.

I’ve confirmed my gcloud secret, etc. it is all correct.

I’ve confirmed the other Firebase services are working.

I’ve given all users invoker access.

I clear my cache on every redeployment attempt.

What am I missing?

Thank you in advance for any help.

Screenshot of error messages

LGChat file:


  const processDocument = async () => {
    if (!selectedFile || isProcessing || isProcessed || !currentUser) return;

    setIsProcessing(true);
    setError(null);
    setProcessingStatus('Starting processing...');

    try {
      // Use the already initialized functions instance from above
      const processPdfDocument = httpsCallable(functions, 'processPdfDocument', {
        timeout: 540000
      });

      console.log('Processing document with auth:', {
        uid: currentUser?.uid,
        email: currentUser?.email
      });

      console.log('Document details being sent:', {
        fileId: selectedFile.id,
        fileName: selectedFile.name,
        fileUrl: selectedFile.url,
        userId: currentUser.uid
      });

      setProcessingStatus('Processing PDF...');
      console.log('Processing PDF...');
      const result = await processPdfDocument({
        fileId: selectedFile.id,
        fileName: selectedFile.name,
        fileUrl: selectedFile.url,
        userId: currentUser.uid
      });
      console.log('Processing PDF result:', result);

      if (result.data?.success) {
        setIsProcessed(true);
        setProcessingStatus(
          `Successfully processed ${result.data.pagesProcessed} pages`
        );
      } else {
        throw new Error(
          'Processing failed: ' + (result.data?.error || 'Unknown error')
        );
      }
    } catch (error) {
      console.error('Error processing document:', error);
      setError(error.message || 'Failed to process document.');
      setIsProcessed(false);
      setProcessingStatus('Processing failed');
    }
  };

functions/index.js:

// Process PDF
exports.processPdfDocument = onCall(
  {
    memory: "2GiB",
    timeoutSeconds: 540,
    cors: true,
    enforceAppCheck: false,
    secrets: [
      "OPENAI_API_KEY",
      "PINECONE_API_KEY",
      "PINECONE_INDEX_NAME",
      "PINECONE_HOST"
    ]
  }, 
  async (request, context) => {
    // Log the full context and request for debugging
    console.log("[processPdfDocument] Request data:", {
      auth: request.auth,
      rawRequest: request.rawRequest,
      data: request.data
    });

    console.log("[processPdfDocument] Context:", {
      auth: context.auth,
      rawRequest: context.rawRequest
    });

    // Check auth
    if (!request.auth) {
      console.error("[processPdfDocument] No auth context");
      throw new HttpsError(
        "unauthenticated",
        "The function must be called while authenticated."
      );
    }

    // Verify UID
    if (!request.auth.uid) {
      console.error("[processPdfDocument] No UID in auth context");
      throw new HttpsError(
        "unauthenticated",
        "Invalid authentication. Please sign in again."
      );
    }

    try {
      // Verify the token
      const decodedToken = await admin.auth().verifyIdToken(context.auth.token);
      console.log("[processPdfDocument] Token verified:", decodedToken.uid);

      console.log("[processPdfDocument] Auth context:", {
        hasAuth: Boolean(context.auth),
        uid: context.auth ? context.auth.uid : null,
        token: context.auth ? Boolean(context.auth.token) : false,
        app: Boolean(context.app)
      });

      if (!request.auth || !request.auth.uid) {
        console.error("[processPdfDocument] Authentication error: No valid auth context");
        throw new HttpsError(
          "unauthenticated",
          "The function must be called while authenticated."
        );
      }

      try {
        console.log("[processPdfDocument] Request data:", {
          ...request.data,
          auth: {uid: context.auth.uid}
        });

        const {fileId, fileName, path} = request.data;
        const uid = context.auth.uid;

        // Validate parameters with detailed logging
        const missingParams = [];
        if (!fileId) missingParams.push("fileId");
        if (!fileName) missingParams.push("fileName");
        if (!path) missingParams.push("path");

        if (missingParams.length > 0) {
          const errorMsg = `Missing required parameters: ${missingParams.join(", ")}`;
          console.error("[processPdfDocument] Parameter validation failed:", {
            received: {fileId, fileName, path},
            missing: missingParams
          });
          throw new HttpsError("invalid-argument", errorMsg);
        }

        // Validate config with error handling
        let config;
        try {
          config = getConfig();
          console.log("[processPdfDocument] Configuration validated successfully");
        } catch (configError) {
          console.error("[processPdfDocument] Configuration error:", configError);
          throw new HttpsError(
            "failed-precondition",
            `Configuration error: ${configError.message}`
          );
        }

        // Initialize storage and get file
        const storage = getStorage();
        const bucket = storage.bucket();
        const tempFilePath = `/tmp/${fileId}-${Date.now()}.pdf`;

        // Download file with detailed error handling
        try {
          console.log("[processPdfDocument] Attempting to download file:", {path, tempFilePath});
          await bucket.file(path).download({destination: tempFilePath});
          console.log("[processPdfDocument] File downloaded successfully");
        } catch (downloadError) {
          console.error("[processPdfDocument] Download error:", {
            error: downloadError,
            path,
            tempFilePath
          });
          throw new HttpsError(
            "internal",
            `Failed to download file: ${downloadError.message}`
          );
        }

        // Process PDF with error handling
        let pdfContent;
        try {
          const dataBuffer = await fs.readFile(tempFilePath);
          console.log("[processPdfDocument] File read successfully, size:", dataBuffer.length);
          
          pdfContent = await pdf(dataBuffer, {
            pageNumbers: true,
            normalizeWhitespace: true,
            disableCombineTextItems: false
          });
          console.log("[processPdfDocument] PDF parsed successfully, pages:", pdfContent.numpages);
        } catch (pdfError) {
          console.error("[processPdfDocument] PDF processing error:", pdfError);
          throw new HttpsError(
            "internal",
            `Failed to process PDF: ${pdfError.message}`
          );
        }

        // Create text chunks
        const splitter = new RecursiveCharacterTextSplitter({
          chunkSize: 1000,
          chunkOverlap: 200
        });

        let allDocs = [];
        try {
          const docs = await splitter.createDocuments(
            [pdfContent.text],
            [{
              pageNumber: 1,
              fileId,
              fileName,
              userId: uid
            }]
          );
          allDocs = docs;
          console.log("[processPdfDocument] Created chunks:", allDocs.length);
        } catch (splitError) {
          console.error("[processPdfDocument] Text splitting error:", splitError);
          throw new HttpsError(
            "internal",
            `Failed to split text: ${splitError.message}`
          );
        }

        // Initialize Pinecone with error handling
        let pineconeIndex;
        try {
          const pineconeOptions = {
            apiKey: config.pineconeApiKey
          };
          if (config.pineconeHost) {
            pineconeOptions.controllerHostUrl = config.pineconeHost;
          }
          const pinecone = new Pinecone(pineconeOptions);
          pineconeIndex = pinecone.index(config.pineconeIndexName);
          console.log("[processPdfDocument] Pinecone initialized successfully");
        } catch (pineconeError) {
          console.error("[processPdfDocument] Pinecone initialization error:", pineconeError);
          throw new HttpsError(
            "internal",
            `Failed to initialize Pinecone: ${pineconeError.message}`
          );
        }

        // Create and store embeddings
        try {
          const embeddings = new OpenAIEmbeddings({
            openAIApiKey: config.openaiApiKey,
            batchSize: 100
          });

          await PineconeStore.fromDocuments(allDocs, embeddings, {
            pineconeIndex,
            namespace: uid,
            maxConcurrency: 5
          });
          console.log("[processPdfDocument] Documents stored in Pinecone successfully");
        } catch (embeddingError) {
          console.error("[processPdfDocument] Embedding/storage error:", embeddingError);
          throw new HttpsError(
            "internal",
            `Failed to create/store embeddings: ${embeddingError.message}`
          );
        }

        // Cleanup temp files
        try {
          await fs.unlink(tempFilePath);
          console.log("[processPdfDocument] Cleaned up temporary file");
        } catch (cleanupError) {
          console.warn("[processPdfDocument] Cleanup warning:", cleanupError);
          // Don't throw on cleanup errors
        }

        return {
          success: true,
          chunksProcessed: allDocs.length,
          pagesProcessed: pdfContent.numpages || 1
        };

      } catch (error) {
        console.error("[processPdfDocument] Top-level error:", {
          message: error.message,
          code: error.code,
          stack: error.stack
        });
        
        if (error instanceof HttpsError) {
          throw error;
        }
        
        throw new HttpsError(
          "internal",
          `Processing failed: ${error.message}`
        );
      }
    } catch (error) {
      console.error("[processPdfDocument] Token verification failed:", error);
      throw new HttpsError(
        "unauthenticated",
        "Invalid authentication token. Please sign in again."
      );
    }
  }
);

2

Answers


  1. The tooling that you are using is confusing the v1 and v2 versions of the Firebase Functions SDK.

    The signature of onCall(options, handler) in the firebase-functions/v2 API (the current default) is:

    export declare function onCall<T = any, Return = any | Promise<any>>(opts: CallableOptions, handler: (request: CallableRequest<T>, response?: CallableProxyResponse) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;
    

    Of particular note, the handler parameter has the type:

    (request: CallableRequest<T>, response?: CallableProxyResponse) => Return
    

    The signature of onCall(options, handler) in the firebase-functions/v1 API (legacy) is:

    export declare function onCall(handler: (data: any, context: CallableContext) => any | Promise<any>): HttpsFunction & Runnable<any>;
    

    Of particular note, the handler parameter has the type:

    (data: any, context: CallableContext) => any | Promise<any>
    

    In your code, you are currently using:

    async (request, context) => {
      // ...
    }
    

    Because the second parameter (mislabelled as context) is a CallableProxyResponse object (not a CallableContext object), there are no auth or rawRequest properties on it.

    This means that context.auth and context.rawRequest both return undefined. Because undefined values are hidden from JSON, you should notice that your Cloud Functions logs have a message that looks like this:

    INFO: [processPdfDocument] Request data: { auth: { uid: "<userid>", token: "..." }, rawRequest: { ... }, data: { fileId: "...", fileName: "...", fileUrl: "...", userId: "<userid>" }}
    
    INFO: [processPdfDocument] Context: {}
    

    Ultimately, this means that your error is thrown by the following line:

    const decodedToken = await admin.auth().verifyIdToken(context.auth.token);
    

    which should produce an Cloud Functions error message similar to:

    ERROR: [processPdfDocument] Token verification failed: Uncaught TypeError: Cannot read properties of undefined (reading 'token')
    

    This is produced by these lines at the bottom of your functions/index.js file:

    } catch (error) {
      console.error("[processPdfDocument] Token verification failed:", error); // <-- logged on server
      throw new HttpsError(
        "unauthenticated",
        "Invalid authentication token. Please sign in again." // <-- logged on client
      );
    }
    

    This "Invalid authentication token. Please sign in again." error message matches what you see on the client.


    To fix this, get rid of the confusing context parameter from your handler function (you are unlikely to ever need the CallableProxyResponse object) and remove all uses of context throughout the code.

    async (request) => {
      // ...
    }
    

    Then fix your verifyIdToken line to use:

    // Verify the token
    const decodedToken = await admin.auth().verifyIdToken(request.auth.token);  // context --> request
    console.log("[processPdfDocument] Token verified:", decodedToken.uid);
    

    You should also consider moving the catch block responsible for this error closer to the verifyIdToken call, rather than have it all the way at the bottom of the file.

    Login or Signup to reply.
  2. Since you’re using a callable type function, there is no need to use the Firebase Admin SDK to verify an auth token like this:

    // This is not necessary with Firebase callable functions
    const decodedToken = await admin.auth().verifyIdToken(context.auth.token);
    

    This verification happens automatically with callable functions. The client automatically sends the token for the current user, and the function framework atomatically verifies it and provided its decoded data. There is no value in trying to repeat that verification. All you need to do is check if the user token was previously verified, as illustrated in the documentation:

    // Authentication / user information is automatically added to the request.
    const uid = request.auth.uid;
    const name = request.auth.token.name || null;
    const picture = request.auth.token.picture || null;
    const email = request.auth.token.email || null;
    

    It seems you are already doing this in your function, so you should just remove the use of the Admin SDK and use request.auth to find out if the user is valid.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search