Search code examples
firebasegoogle-analyticsgoogle-analytics-apifirebase-analytics

Firebase: Delete user analytics data - userdeletionRequests:upsert - GDPR


My question is, how can I delete a users analytics data from firebase using "Google User Deletion API" and its method: userdeletionRequests:upsert? This is important for me to fully fulfill GDPR.

I tried searching for this, but didn't a solution for using it in combination with "NodeJS" and "firebase-cloud-functions".

My biggest problem is, how I get the access, token, this is what I have for now:

const accessToken = (await admin.credential.applicationDefault().getAccessToken()).access_token;

return ky.post(constants.googleUserDeletionURL, {
    body: JSON.stringify({
        kind: "analytics#userDeletionRequest",
        id: {
            type: "USER_ID",
            userId: uid,
          },
          propertyId: constants.googleUserDeletionPropertyId,
    }),
    headers: {
        "Authorization": `Bearer ${accessToken}`,
    },
}).catch((err) => {
    functions.logger.log(`An Error happened trying to delete user-anayltics ${(err as Error).message}`);
});

But I always get An Error happened trying to delete user-anayltics Request failed with status code 403 Forbidden


Solution

  • Okay, after some painful and long days (literally took me like >20 hours), I've figured out how to achieve this request. Here is a step for step guide:

    Step 1 - Needed Dependencies

    To send a post-request to google, we need a http-client-library. I've choosen "ky", so we need to install it first with:

    npm install ky
    

    Furthermore, we need to create or OWN oAuth2 token, otherwise the post request will be denied with "error 403". To create our own oAuth token, we need another dependency:

    npm install @googleapis/docs
    

    Step 2 - Needed Google Property ID

    With your request, Google needs to know which property-id / project you are targeting (where it should later delete the user-analytics-data). To get this property-id, we need to log in into GCP via Firebase (not the "real" GCP, the one via Firebase).

    For this, go into your "console.firebase.google.com" → Select your project → Analytics Dashboard → "View more in Google Analytics (button at the right corner)"

    enter image description here

    Write "property-id" into the search field and then save it somewhere (we need it later)

    enter image description here

    Step 3 - Creating Client-Secret

    The third step is to create a service-account, which we will later add into our functions-folder, in order to create our oAuthClient (don't worry, you will see what I mean to a later point)

    To create your new service.json, log in to google cloud platform via "https://cloud.google.com/" and then follow the pictures:

    FIRST:

    enter image description here

    SECOND:

    enter image description here

    THIRD:

    enter image description here

    FOURTH:

    enter image description here

    FIFTH

    enter image description here

    Step 4 - Download JSON

    After we created our "oAuth-Deleter service-account", we need to manually download the needed JSON, so we can paste it into our functions-folder.

    For this, select "[email protected]" under "Service Account"

    enter image description here

    Then click on "Keys" and "Add key", which will automagically download you a service-json (SELECT Key type → JSON → Create).

    enter image description here

    enter image description here

    Step 5 - Paste JSON file into your functions-folder

    To loosen up the mood a bit, here is an easy step. Paste the downloaded JSON-File into your functions-folder.

    enter image description here

    Step 6 - Grant Access to our new created oAuth-Delelter-Account

    Creating the service-account and giving it access in the normal GCP is not enough for Google, so we also have to give it access in our Firebase project. For this, go back into your "GCP via Firebase (see Step 2)" → Click on Setting → "User Access for Account" → Click on the "plus"

    enter image description here

    Then click on "Add user" and write the email we copied before into the email field (the email from Step 3, Picture FOURTH "Service-Account ID). In our case, it is "[email protected]". Furthermore, it needs admin-access:

    enter image description here

    Step 6 - The code

    Now, after these million unnecessary but necessary steps, we get to the final part. THE DAMN CODE. I've written this in typescript with "compilerOptions" → "module": "esnext", "target": "esnext". But I am sure that you are smart enough to change the code after completing this many steps :)

    import admin from "firebase-admin";
    import functions from "firebase-functions";
    import ky from "ky";
    import docs from "@googleapis/docs";
    import { UserRecord } from "firebase-functions/v1/auth";
    
    export const dbUserOnDeleted = functions.
       .auth
       .user()
       .onDelete((user) => doOnDeletedUser(user))
    
        ----------------------------
    
    export asnc function doOnDeletedUser/user: UserRecord) {
       try {
           const googleDeletionURL = "https://www.googleapis.com/analytics/v3/userDeletion/userDeletionRequests:upsert"
           
           // Step 1: Paste propertyID: (See Step 2)
           const copiedPropertyID = "12345678"
    
           // Step 2: Create oAuthClient
           const oAuthClient = new docs.auth.GoogleAuth({
               keyFile: "NAME-OF-THE-FILE-YOU-COPIED-INTO-YOUR-FUNCTIONS-FOLDER",
               scopes: ["https://www.googleapis.com/auth/analytics.user.deletion"]
           });
    
           // Step 3: Get user uid you want to delete from user-analytics
           const uid = user.uid
    
          // Step 4: Generate access token 
          // (this is the reason why we needed the 5 steps before this)
          // yup, a single line of code
          const accessToken = await oAuthClient.getAccessToken() as string;
    
          // Step 5: Make the request to google and delete the user
          return ky.post(googleDeletionURL, {
              body: JSON.stringify({
                  kind: "analytics#userDeletionRequest",
                  id: {
                     type: "USER_ID",
                     userid: uid
                  },
                  propertyId: copiedPropertyID
              }),
              headers: {
                  "Authorization": "Bearer " + accessToken,
              }
          });
    
       } catch (err) {
         functions.logger.error(`Something bad happened, ${(err as Error).message)`
       }
    }
    

    Afterthoughts

    This was and probably will be my longest post at stack overflow forever. I have to say that it was a pain in the a** to get this thing to working. The amount of work and setup that is needed to simply delete a data from one endpoint is just ridiculous. Google, please fix.