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.
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
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 thefirebase-functions/v2
API (the current default) is:Of particular note, the
handler
parameter has the type:The signature of
onCall(options, handler)
in thefirebase-functions/v1
API (legacy) is:Of particular note, the
handler
parameter has the type:In your code, you are currently using:
Because the second parameter (mislabelled as
context
) is aCallableProxyResponse
object (not aCallableContext
object), there are noauth
orrawRequest
properties on it.This means that
context.auth
andcontext.rawRequest
both returnundefined
. Becauseundefined
values are hidden from JSON, you should notice that your Cloud Functions logs have a message that looks like this:Ultimately, this means that your error is thrown by the following line:
which should produce an Cloud Functions error message similar to:
This is produced by these lines at the bottom of your
functions/index.js
file: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 theCallableProxyResponse
object) and remove all uses ofcontext
throughout the code.Then fix your
verifyIdToken
line to use: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.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 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:
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.