0

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);
  • Can you `console.log(req.headers.authorization)` and share the output? Also how are you passing the token in your API request? Can you share the frontend code for the same? – Dharmaraj Jun 04 '22 at 13:30
  • I added some more information based on your questions – Miggle Sizzle Jun 04 '22 at 14:47
  • you're doing wrong when mixing async/await pattern with Promises & method chaining. Use one or another, check if idToken was received before passing the value to firebase calls. – Jone Polvora Jun 05 '22 at 13:36
  • Thanks! Turning promise & method chaining to an async/await pattern worked! – Miggle Sizzle Jun 05 '22 at 17:31

1 Answers1

1

Thanks to Jone Polvora and this article on converting promise chains to async/await.

In my authMiddleware.js, I turned the promise and method chain to an async/await pattern.

From this

    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)
    }
  }

To this

    try {
      idToken = req.headers.authorization.split(' ')[1]

      let decodedToken = await getAuth().verifyIdToken(idToken)
      let uid = await decodedToken.uid
      req.user = uid
      
      next()
    } catch (error) {
      console.log(error)
      res.status(401)
      throw new Error(error)
    }
  }