4

I am using Google Endpoints as an API gateway which is running in a Google Run container service. The API path points to a Google Function (node js). The calls to the API gateway are from a web application (viz. browser).

One of the paths is: /login which authenticates a user in firebase using the firebase.auth().signInWithEmailAndPassword method. I get the token Id of the user and send it back in the response header (authentication bearer) back to the browser. This works as expected.

When other Requests are made (e.g /check) to the endpoint the token (in the header) is included. I wanted to check the validity of the token using the Firebase Admin method before processing any requests. The code in the Google Function that does this for one of the routes is as follows:

...

const decodeIdToken = async (req, res, next) => {
  // Read the ID Token from the Authorization header.
  const idToken = req.headers.authorization.split('Bearer ')[1];
  try {
    const decodedIdToken = await admin.auth().verifyIdToken(idToken);
    req.decodedToken = decodedIdToken;
    next();
    return;
  } catch (error) {
    return res.status(403).json({
      status: 'failure',
      data: null,
      error: error.message
    });
  }
};

// use decodeIdToken as middleware
app.post('/check', decodeIdToken, (req, res, next) => {
  return res.json({
    status: 'success',
    data: req.decodedToken,
    error: null
  });
});

When I call (via Postman ) the routes by directly calling the Google Function trigger both the routes work. However, when I call the Google Endpoints which point to the Google Function I receive the following error while using the Firebase Admin object:

Firebase ID token has incorrect \"aud\" (audience) claim. Expected \"PROJECT-ID\" but got \"https://us-central1-PROJECT-ID.cloudfunctions.net/FUNCTION-NAME\". Make sure the ID token comes from the same Firebase project as the service account used to authenticate this SDK. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token

When setting the Firebase Admin object in NodeJs I tried the following:

const admin = require('firebase-admin');
admin.initializeApp();

as well as

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://PROJECT-ID.firebaseio.com"
});
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
RmR
  • 1,917
  • 1
  • 22
  • 35

3 Answers3

2

Use the X-Apigateway-Api-Userinfo Header

The header's value is the base64 encoded payload of the JWT. There's no need to reverify as API Gateway already verified the token for you and has made the contents available for your use.

Example for Node.js devs:

Buffer.from(req.header("x-apigateway-api-userinfo"), "base64").toString();

If for whatever reason you do need access to the original JWT, it is available in the X-Forwared-Authorization header.


Unnecessary Extra Credit:

To explain the error, the reason you are getting the wrong Audience claim is because the JWT you are trying to verify is a different JWT generated by API Gateway. The original Authorization Header has been replaced with this JWT. Why? It is telling Cloud Functions "Hey Cloud Function, it's me API Gateway that's calling you and here's a signed JWT to prove it". Hence API Gateway's audience ends up being the Cloud Function resource url whereas Firebase's audience is the Project the Firebase sits in.

Just another example of weird inconveniences due to Google's implementation if you ask me; they could have definitely left the Auth header untouched and had API Gateway use a different header, but beggars can't be choosers. ‍♂️


Reference API Gateway Documentation:

Receiving authenticated results in your API

API Gateway usually forwards all headers it receives. However, it overrides the original Authorization header when the backend address is specified by x-google-backend in the API config.

API Gateway will send the authentication result in the X-Apigateway-Api-Userinfo to the backend API. It is recommended to use this header instead of the original Authorization header. This header is base64url encoded and contains the JWT payload.

Govind Rai
  • 14,406
  • 9
  • 72
  • 83
0

The following worked does not work (see comment below):

In the openapi-functions.yaml add the security defintion as recommended by the docs

securityDefinitions:
    firebase:
      authorizationUrl: ""
      flow: "implicit"
      type: "oauth2"
      # Replace YOUR-PROJECT-ID with your project ID
      x-google-issuer: "https://securetoken.google.com/YOUR-PROJECT-ID"
      x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com"
      x-google-audiences: "YOUR-PROJECT-ID"

Then, against the path (/check in my case), add the security section as below:

   /check:
      post:
        ...
        x-google-backend:
           ....
           ....
        security:
           - firebase: []
        ....

Refer to: https://cloud.google.com/endpoints/docs/openapi/authenticating-users-firebase

RmR
  • 1,917
  • 1
  • 22
  • 35
  • The above does not work. I mistakenly thought it does. The error it shows is that the "aud" is the google function rather than the project-id. The google function is not called but gets rejected by the Google API gateway – RmR Jan 23 '20 at 10:43
  • 1
    No. @DecentGradient. Since then I moved away from this config. So am not clued in – RmR Feb 08 '21 at 11:31
0

There isn't problem with your admin-sdk settings, it's the idToken which is actually a jwt token retured as idToken while sign in using firebase.

Your problem is you are trying to use the JWT token returned as idToken by one of the auth() functions like firebase.auth().signInWithEmailAndPassword These do return a JWT token, however the auth claims will likely be wrong and won't pass verification by verifyIdToken. Firebase tech support confirmed this.

You have to use the firebase.auth().currentUser.getToken() function. That token will pass verification.

 const idToken=await firebase.auth().currentUser.getToken()
Dhaval Italiya
  • 386
  • 2
  • 7