3

I have a simple flask app in a Google Cloud Function that returns different data based on the user's authorization, specified by a token in the header:

@functions_framework.http
def main(request):
    token = request.headers['Authorization']
    data: List[str] = my_store.get_authorized_data(token)
    print(data)
    return data

This is deployed using:

gcloud functions deploy myfunc \
--entry-point main \
--project=myproj \
--trigger-http \
--runtime=python39 \
--allow-unauthenticated \
--timeout=540 \
--memory=1024MB \
--min-instances 1

I then have a React/TypeScript front-end deployed to Firebase hosting that calls the function:

axios.get(`cloud/function/url`, {
  headers: {
    Accept: "application/json, text/plain, */*",
    Authorization: `Bearer ${token}`,
  },
})
.then((result) => {
  console.log(result.data);
  return result.data;
});

The cloud functions are regular Google Cloud Functions, not Cloud Functions for Firebase.

The problem

If I send a request with a token authorized for all data ['a', 'b', 'c'], everything works fine:

  • Cloud function log: ['a', 'b', 'c']
  • Front-end log: ['a', 'b', 'c']

If I then switch to a user with less data access, just ['a', 'b'], it also works:

  • Cloud function log: ['a', 'b']
  • Front-end log: ['a', 'b']

But if I then switch back to the account with access to all data, there is an unexpected result:

  • Cloud function log: ['a', 'b', 'c']
  • Front-end log: ['a', 'b']

The front-end is not receiving the data that the cloud function is logging. This is intermittent, if I make repeat calls, around half get the correct ['a', 'b', 'c'] in the front-end, the other half always show the incorrect ['a', 'b']. All logs in the cloud function show the correct ['a', 'b', 'c'] regardless of the result in the front-end.

It appears the cloud function is caching the result, and just sending an old result.

What I've tried

A similar question states that cloud functions re-use server instances and there's not any remedy.

Here are the steps I've taken:

  1. Redeploying the cloud function: this works, the issue disappears, but returns once I switch tokens again. I can't redeploy my cloud function every time a user needs to change account.
  2. Redeploying the front-end to Firebase: this does not fix the issue.
  3. Disabling cache in the browser: this does not fix the issue.
  4. Disabling cache in firebase hosting (as in this question): this does not fix the issue, similarly I have tried setting this in my firebase.json, which also did not fix the issue:
"headers": [{
    "source": "**",
        "headers": [{
            "key": "Cache-Control",
            "value": "max-age=0"
        }]
    }]
  1. Disabling caching on the axios.get: this does not fix the issue. I tried 3 different methods as in this question: by adding an ever-changing parameter to the URL so it is always different, setting such a parameter in the call params and setting the caching headers directly. None of these fix the issue:
axios.get(`/cloud/function/url?timestamp=${new Date().getTime()}`, {
  headers: {
    Accept: "application/json, text/plain, */*",
    Authorization: `Bearer ${token}`,
    'Cache-Control': 'no-cache',
    Pragma: 'no-cache',
    Expires: '0',
  },
  params: {
    t: new Date().getTime()
  }
})

From my disabling front-end cache, redeploying front-end and the console.log I am confident the issue is not client-side. I curl the API directly to verify this:

curl '/my/function/url' -H 'Authorization: Bearer '"$token"'
  1. Response for token 1: {data: ["a", "b", "c"]}
  2. Response for token 2: {data: ["a", "b"]}
  3. Response going back to token 1: {data: ["a", "b"]}

It seems clear the cloud function really is returning some type of cached response. This is further reinforced by the fact that redeploying the cloud function does fix the issue. How do I stop a Google Cloud Function from caching responses?


Edit: further details on how the backend retrieves the data for each token

Data is stored in a Firestore NoSQL document database. Each entry has the user account as the document name and the allowed data as fields in the document.

user1: {a: null, b: null, c: null}
user2: {a: null, b: null}

The get_authorized_data function gets the user account from the token and retrieves the allowed data items from the Firestore:

from firebase_admin import auth, firestore

db = firestore.client()

def get_authorized_data(token):
    # This returns {'uid': '123', 'email': 'example@example.com'}
    decoded_token = auth.verify_id_token(id_token)
    document = db.collection(u'allowed-data-per-user')\
               .document(decoded_token['email']).get()
    allowed_data = list(document.to_dict().keys())
    return allowed_data

If you notice my comments about the print statement in the first block of python at the top of the question, this retrieves the correct data: always ['a', 'b', 'c'] for token 1 and always ['a', 'b'] for token 2. I do not believe that this is the part at fault.

Student
  • 522
  • 1
  • 6
  • 18
  • 3
    Cloud Functions does not have cache. Of course if you store the data in a global variable (and not restricted to the function entry point scope) you can keep data, but your test demonstrates that's not the case. The issue should come from the front part. Convince yourselves by invoking your function with a curl. Focus your effort on the front part – guillaume blaquiere Jan 05 '23 at 17:55
  • @guillaumeblaquiere curling the API has the same problem. `['a', 'b', 'c']` with token 1, `['a', 'b']` with token 2, then back to token 1: `['a', 'b']`. It absolutely seems that it is the cloud function that is returning some type of cached response. – Student Jan 06 '23 at 11:02
  • So, we need more code on your function, especially the "my_store" object, and how it works. – guillaume blaquiere Jan 06 '23 at 11:23
  • caching can happen on multiple levels. The easiest way to prevent caching is to add parameters to your request, Simply adding a `?` will work. Official name for this is "cache-busting" I think – Edo Akse Jan 06 '23 at 11:32
  • @guillaumeblaquiere Thank you for your response! I have added another section above detailing how it works. – Student Jan 06 '23 at 11:51
  • @EdoAkse Thank you! See 4. and 5. above - I have already attempted multiple versions of this. – Student Jan 06 '23 at 11:51

0 Answers0