1

I have simple test callable function deployed via firebase deploy --only functions.

Here it's content:

export const test = region('europe-west1').https.onCall((data, context) => {
    logger.debug('test call info', data, context);
    return 'hello, world!';
});

I can successfully call it by doing HTTP call to https://europe-west1-{project}.cloudfunctions.net/test and recieve {"result": "hello, world!"}.

Now want to protect this function via IAM policies.

  1. I create new service account and load it's credentials json.
  2. I go to console and remove from allUsers a Cloud Functions Invoker role and add service account created in #1 with Cloud Functions Invoker role. <--- Now I recieve 403 Forbidden when I try to call my test function, as expected
  3. I create id-token with my service account at my PC
const auth = new GoogleAuth({keyFile: './key.json'});
targetAudience = new URL('https://europe-west1-{project}.cloudfunctions.net/test');
const client = await auth.getIdTokenClient(targetAudience as string);
const idToken = await client.idTokenProvider.fetchIdToken(targetAudience as string);
console.log(idToken);
  1. I verify my token is correct and issued for the right service account via https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={{id-token}}.
  2. I call my endpoint with Authorization: Bearer {{id-token}} header.
  3. I get 401 Unauthorized error with JSON telling me
{
    "error": {
        "message": "Unauthenticated",
        "status": "UNAUTHENTICATED"
    }
}
  1. I'm in pain since after reading most of google cloud docs, SO (1, 2), etc. it seems that I do everything right but still get this error.

UPD1: providing additional info to @DazWilkin

1.

gcloud auth activate-service-account cloud-functions-invoker@{project-id}.iam.gserviceaccount.com --key-file="key.json" 

Activated service account credentials for: [cloud-functions-invoker@{project-id}.iam.gserviceaccount.com]
gcloud projects get-iam-policy {project-id}

...
- members:
  - serviceAccount:cloud-functions-admin@{project-id}.iam.gserviceaccount.com
  - serviceAccount:cloud-functions-invoker@{project-id}.iam.gserviceaccount.com
  role: roles/cloudfunctions.invoker
...
  1. I copy this value gcloud auth print-identity-token | clip
  2. Here is what I get after calling https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={{id-token}}
{
    "aud": "32555940559.apps.googleusercontent.com",
    "azp": "cloud-functions-invoker@{project-id}.iam.gserviceaccount.com",
    "email": "cloud-functions-invoker@{project-id}.iam.gserviceaccount.com",
    "email_verified": "true",
    "exp": "1629157648",
    "iat": "1629154048",
    "iss": "https://accounts.google.com",
    "sub": "114693730231812960252",
    "alg": "RS256",
    "kid": "6ef4bd908591f697a8a9b893b03e6a77eb04e51f",
    "typ": "JWT"
}
  1. I make this curl (copied from postman). I've also tried same curl with command-line - got same result.
curl --location --request POST 'https://europe-west1-{project-id}.cloudfunctions.net/test' \
--header 'Authorization: Bearer 
{token}
' \
--header 'Content-Type: application/json' \
--data-raw '{
    "data": {
        "goodbye": "world:("
    }
}'

UPD2: new input

This behaviour is observed only with callable functions https.onCall(). If I use https.onRequest() things work fine but I'm still confused about onCall behaviour, since it seems that I implement protocol correctly (see my curl).

12
  • 1
    It appears (!?) correct. You can use gcloud to [gcloud auth print-identity-token ](cloud.google.com/sdk/gcloud/reference/auth/print-identity-token) after gcloud auth activate-service-account@](cloud.google.com/sdk/gcloud/reference/auth/…). This will eliminate whether your client generation of the identity token is incorrect. Although your check using the endpoint does provide a good way to check. Commented Aug 16, 2021 at 15:48
  • You don't need to delete the existing account just add your newly created account to the IAM invoker role Commented Aug 16, 2021 at 15:49
  • 1
    Perhaps include (edited) the result of gcloud projects get-iam-policy for the project to confirm the invoker binding. Commented Aug 16, 2021 at 15:54
  • 1
    IAM policy looks good! Commented Aug 16, 2021 at 23:42
  • 1
    If you're just using {token} in lieu of the value in your question, then I don't understand why it isn't working! Commented Aug 16, 2021 at 23:45

1 Answer 1

10

OMG!!!!!

TL;DR: callable functions are only for end users authenticated with firebase auth and can't be used with IAM tokens. Only way to use them is with roles/cloudfunctions.invoker assigned to allUsers.


The thing is... I was looking into callable functions protocol specification here: https://firebase.google.com/docs/functions/callable-reference.

It says:

The HTTP request to a callable trigger endpoint must be a POST with the following headers:

...

  • Optional: Authorization: Bearer
    • A Firebase Authentication user ID token for the logged-in user making the request. The backend automatically verifies this token and makes it available in the handler's context. If the token is not valid, the request is rejected.

I was thinking specification means that we shall pass our service account id-token here and I was trying to do so (such as with onRequest functions). BUT SUDDENLY, token is interpreted differently there. It's a user token which is obtained through firebase auth. And it's even stated in the documentation one level deeper (1, 2):

Warning: The ID token verification methods included in the Firebase Admin SDKs are meant to verify ID tokens that come from the client SDKs, not the custom tokens that you create with the Admin SDKs. See Auth tokens for more information.

Missing this warning costed me 3 days of pain.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.