I have a Flutter app that signs users in with Firebase Auth. Right after sign-in, I call:
final user = FirebaseAuth.instance.currentUser;
final idToken = await user?.getIdToken();
Then I send that token to my Node/Next.js server for verification. However, even though the token is newly fetched, the server logs show:
"Invalid or expired token"
401 Unauthorized
On Firebase’s side, the logs (e.g., in “identitytoolkit.googleapis.com/requests”) confirm:
"iss": "https://securetoken.google.com/my-app",
"aud": "my-app",
"exp": 1735790608,
Indicating the token should be valid for the my-app project, at least until the exp time.
But in my Node code, I have something like:
import * as admin from 'firebase-admin';
import serviceAccount from '/opt/fb/auth.json';
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://my-app.firebaseio.com',
// or possibly projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID
});
}
export default async function handler(req, res) {
const { token } = req.query;
if (!token) return res.status(400).send('No token found');
try {
const decodedToken = await admin.auth().verifyIdToken(token);
// never hits here; jumps to catch
...
} catch (error) {
console.error(error); // => always "Invalid or expired token"
return res.status(401).send('Invalid or expired token');
}
}
Why would a brand-new token from the correct Firebase project keep failing with “Invalid or expired token”?
Additional Clues
My .env has NEXT_PUBLIC_FIREBASE_PROJECT_ID=my-app.
The JSON service account file (/opt/fb/auth.json)
is the correct project
My server time (checked with date or ntpdate) looks correct. How do I fix this so verifyIdToken() actually accepts my fresh token?
I found a solution and posted here: https://stackoverflow.com/a/79332008/2163927
Go to Google Cloud Console → IAM & Admin → Service Accounts
Edit or Permissions then add the “Firebase Admin SDK Administrator Service Agent” or “Firebase Authentication Admin” role