2
votes

I've deployed this exact function from the google cloud function tutorial to my google cloud project.

On the client side an end user is authenticated using the firebase signInWithPopup authentication. The id token is taken from the resulting user via user.getIdToken().then(token => { ... // ... }.

A call to the endpoint is then made via an angular httpclient call:

const url = 'https://[MY-PROJECT-SUBDOMAIN].cloudfunctions.net/hello_get/';
this.auth.user.getIdToken().then(token => {
    const httpOptions = {
        headers: new HttpHeaders({
            'Authorization': `Bearer ${token}`
        })
    };
    this.http.get(url, httpOptions).subscribe(response => {
        console.log(response);
    }, error => {
        console.error(error);
    });
});

The response is a 403 to the OPTIONS preflight request which makes me think Google is rejecting my ID token. I've added a function log in my hello_get function. It appears the function doesn't even get called because this log is never appearing when the 403 response is returned.

The cloud function works fine via CORS when the function permissions for 'Cloud Functions Invoker' are set to allUsers, but as soon as I delete that permission and add permissions for allAuthenticatedUsers, and try to pass a token with Access-Control-Allow-Credentials': 'true' in the cloud function, a 403 is returned.

So - it seems like the root cause is google rejecting the preflight OPTIONS request. If this is the case, what is the recommended way to call an authenticated google cloud function with an authenticated firebase user?

Note: I'm using google cloud functions instead of firebase functions here because we need to use the python3 runtime which is not available in firebase functions.

1
Hi there! Do you have a service account in the client? From the documentation I understand that you can check if the Service Account has the permissions with this command from the Google Cloud Shell curl https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME \ -H "Authorization: bearer $(gcloud auth print-identity-token --impersonate-service-account=[ServiceAccount])"Juancki
I do have the service account in the client. Is the impersonate call necessary if I'm making the api call on the same service account? It seems to work either way using curl. As soon as I make the call with the web app, using the angular HttpClient, I get a 403 on the preflight OPTIONS call. Reading comments on this post stackoverflow.com/questions/51467977/… makes me wonder if gcloud functions requires the auth header to be included in the preflight request? I'm not seeing it passed in the OPTIONS call.mpkasp
There are 2 differences I see between my curl call and the angular HttpClient request: 1) the id token appears to be different (I think this makes sense as I'm grabbing a user's id token from firebase authentication, versus my service account id token). 2) the browser seems to make a preflight OPTIONS call that curl is not making.mpkasp
Hi, in the link I provided there is a section that is Authenticating with Firebase for the GCP Cloud Functions. here.Juancki
Alright, thank you for pointing me back there. Reading through those docs more carefully, it sounds like when using Firebase Authentication with Google Cloud functions, (1) the IAM settings on the function needs to be opened up to AllUsers (as opposed to allAuthenticatedUsers which I had) and (2) the token verification is done manually using firebase_admin's auth library. I got that hint by reading the note: "Unlike Google Sign-in above, your function is doing the authentication; therefore, you will be billed for unauthenticated requests since the function must do work to validate the token."mpkasp

1 Answers

3
votes

When using Firebase Authentication with Google Cloud functions, users are not authenticated via IAM roles. Instead, they are authenticated within the function itself. Note, per Google's docs, "you will be billed for unauthenticated requests since the function must do work to validate the token." To set this up:

(1) The IAM permissions for "Cloud Functions Invoker" on the function need to be opened up to allUsers (as opposed to allAuthenticatedUsers which I had).

(2) The token verification is then done manually using firebase_admin's auth library, (docs, for reference & other languages) like so:

from firebase_admin import auth

def your_function(request):
    decoded_token = auth.verify_id_token(id_token)
    uid = decoded_token['uid']

Thanks to Juancki for pointing me in the right direction.