I'm using some middleware to authenticate my REST API with a Firebase Auth token and the Firebase Admin SDK.
It works perfectly fine when I send a valid token through.
Sending an expired or invalid token crashes the node server.
Here's the authMiddleware.js code
const protect = asyncHandler(async (req, res, next) => {
let idToken
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
try {
idToken = req.headers.authorization.split(' ')[1]
getAuth()
.verifyIdToken(idToken)
.then((decodedToken) => {
const uid = decodedToken.uid
console.log(`Authenticated user: ${uid}`);
}).catch((error) => {
throw error
})
next()
} catch (error) {
console.log(error)
res.status(401)
throw new Error(error)
}
}
if (!idToken) {
res.status(401)
throw new Error('Not authorized, no token')
}
})
module.exports = { protect }
Sending a Bearing token with nothing in it, the server handles the error fine and doesn't crash.
FirebaseAuthError: First argument to verifyIdToken() must be a Firebase ID token string.
at FirebaseAuthError.FirebaseError [as constructor] (thepathtomyfolder\node_modules\firebase-admin\lib\utils\error.js:44:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (thepathtomyfolder\node_modules\firebase-admin\lib\utils\error.js:90:28)
at new FirebaseAuthError (thepathtomyfolder\node_modules\firebase-admin\lib\utils\error.js:149:16)
at FirebaseTokenVerifier.verifyJWT (thepathtomyfolder\node_modules\firebase-admin\lib\auth\token-verifier.js:118:19)
at BaseAuth.verifyIdToken (thepathtomyfolder\node_modules\firebase-admin\lib\auth\base-auth.js:118:37)
at thepathtomyfolder\backend\middleware\authMiddleware.js:24:10
at asyncUtilWrap (thepathtomyfolder\node_modules\express-async-handler\index.js:3:20)
at Layer.handle [as handle_request] (thepathtomyfolder\node_modules\express\lib\router\layer.js:95:5)
at next (thepathtomyfolder\node_modules\express\lib\router\route.js:144:13)
at Route.dispatch (thepathtomyfolder\node_modules\express\lib\router\route.js:114:3) {
errorInfo: {
code: 'auth/argument-error',
message: 'First argument to verifyIdToken() must be a Firebase ID token string.'
},
codePrefix: 'auth'
}
Here's the error when I send an invalid token (some random string)
FirebaseAuthError: Decoding Firebase ID token failed. Make sure you passed the entire string JWT which represents an ID token. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
at FirebaseAuthError.FirebaseError [as constructor] (thepathtomyfolder\node_modules\firebase-admin\lib\utils\error.js:44:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (thepathtomyfolder\node_modules\firebase-admin\lib\utils\error.js:90:28)
at new FirebaseAuthError (thepathtomyfolder\node_modules\firebase-admin\lib\utils\error.js:149:16)
at thepathtomyfolder\node_modules\firebase-admin\lib\auth\token-verifier.js:180:23
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
errorInfo: {
code: 'auth/argument-error',
message: 'Decoding Firebase ID token failed. Make sure you passed the entire string JWT which represents an ID token. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
},
codePrefix: 'auth'
}
Node.js v18.2.0
[nodemon] app crashed - waiting for file changes before starting...
Here's the error when I send an expired token
FirebaseAuthError: Firebase ID token has expired. Get a fresh ID token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
at FirebaseAuthError.FirebaseError [as constructor] (oat\node_modules\firebase-admin\lib\utils\error.js:44:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (thepathtomyfolder\node_modules\firebase-admin\lib\utils\error.js:90:28)
at new FirebaseAuthError (thepathtomyfolder\node_modules\firebase-admin\lib\utils\error.js:149:16)
at FirebaseTokenVerifier.mapJwtErrorToAuthError (thepathtomyfolder\node_modules\firebase-admin\lib\auth\token-verifier.js:269:20)
at thepathtomyfolder\node_modules\firebase-admin\lib\auth\token-verifier.js:253:25
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
errorInfo: {
code: 'auth/id-token-expired',
message: 'Firebase ID token has expired. Get a fresh ID token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'
},
codePrefix: 'auth'
}
Node.js v18.2.0
[nodemon] app crashed - waiting for file changes before starting...
Help?
EDIT:
When I console.log(req.headers.authorization)
, it returns
Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImY0ZTc2NDk3ZGE3Y2ZhOWNjMDkwZDcwZTIyNDQ2YTc0YjVjNTBhYTkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vbC1saXN0LWFiMjdmIiwiYXVkIjoibC1saXN0LWFiMjdmIiwiYXV0aF90aW1lIjoxNjU0MzM3NDYwLCJ1c2VyX2lkIjoicUJyVjNYR1Nzd2dFYmhxYmhHZXhnaE9uMFlBMiIsInN1YiI6InFCclYzWEdTc3dnRWJocWJoR2V4Z2hPbjBZQTIiLCJpYXQiOjE2NTQzMzc0ODMsImV4cCI6MTY1NDM0MTA4MywiZW1haWwiOiJhQGEuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbImFAYS5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.fwmXiUj2v0KO5m_z40nV8Pqx351vQwTq7hg1AynwPq1dJ095P4YAwQsrsUHEv-svR1MjAavUJz0UdH44SDKPJrAY5DIKMlRXqykQhlTh_XhtaQ9aCw1UbdNYAGl89R07hQWi63fsjyHf_7fuoKpkKXyKSM1dRf0ldhGpPfVzLm70PpBf7yfmwC982vGEVRxQHTnDQndwwFfCU4Sks4kB8yZkQV-FOIn0h11yJ_TBcekxMm6bVS8mu0Om9JA8DEnUVzJO9mkaaencuc2EX-u7Jw4drUmxsUkZQEaEDIr2lBD0ipyN8mSxUVO4wEBNvfWoeC3_U5JXAZIfpgHEe72L_A
React frontend sends a post request with Axios from a dispatch from Redux.
Component sends
dispatch(createL(L));
Which goes here (llistSlice.js)
export const createL = createAsyncThunk('llist/create', async (LData, thunkAPI) => {
try {
let idToken;
await auth.currentUser.getIdToken(true).then(function(googleIdToken) {
idToken = googleIdToken;
});
return await llistService.createL(LData, idToken);
} catch (error) {
const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString();
return thunkAPI.rejectWithValue(message);
};
});
Which goes here (llistService.js)
import axios from 'axios';
const API_URL = '/api/llist';
const createL = async (LData, idToken) => {
const config = {
headers: {
Authorization: `Bearer ${idToken}`,
},
};
const response = await axios.post(API_URL, LData, config);
console.log(response.data);
return response.data;
};
Then it goes through the middleware (as above).
Then through here at the backend
router.post("/", protect, createL);