skip to Main Content

I am upgrading my firebase functions to V2 and I want to use the new way of managing secrets via defineSecret described here:
https://firebase.google.com/docs/functions/config-env?hl=en&gen=2nd

It is not clear to me how this works with onRequest to host a function via express that can be accessed from outside the firebase framework.

Here is a minimal version of my setups (which worked with firebase functions V1):

import express from "express"
import cors from "cors"
import { onRequest } from "firebase-functions/v2/https"

const app = express()

app.use(
  cors({
    origin: function (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) {
      if (!origin) return callback(null, true)
      return callback(null, true)
    },
  }),
)

import { handleNewProfile } from "./handlers/profiles" // needs a secret key for external service like sendgrid


app.post("/profiles/", handleNewProfile)
export const api = onRequest({ region: "europe-west1" }, app) // how to pass secret to app?

I can add the secrets to the first arguement like this:

import { defineSecret } from "firebase-functions/params"
{ region: DEFAULT_REGION, secrets: [secretDiscordToken] }

But how do I hand it down to handleNewProfile?
I have not found this case in the docs and ChatGPT also does not know! 🙂
Any help would be greatly appreciated.

2

Answers


  1. Chosen as BEST ANSWER

    Thanks for the help Doug Stevenson!

    I am including a complete answer since it may be helpful to the next person!

    I first thought I needed to hand down a secret explicitly in the file where it is defined via defineSecret and passed down to a firebase function like onRequest.

    What I learned is, that any function called via onRequest can access the secret. There is no need to hand it down explicitly.

    Below is a working minimal example of how it can work.

     // ./handlers/secretTest.ts
        import { Request, Response } from "express"
        import { defineSecret, defineString } from "firebase-functions/params"
        const blubb = defineString("BLUBB")
        const secretTest = defineSecret("TEST_NOT_REALLY_SECRET")
        
        const printSecret = (req: Request, res: Response) => {
          const envVar = `${blubb.value()}`
          const secret = `${secretTest.value()}`
          res.send(`secret: ${secret} - envVar: ${envVar}!`)
        }
        
        export { printSecret }
    

    Here is the main file where the secret is added to the dependency array of onRequest. Even though there are a few indirections (with using express) until the secret is referenced, it all works:

    // index.ts
    import express, { Request, Response } from "express"
    import cors from "cors"
    import { onRequest } from "firebase-functions/v2/https"
    import { defineSecret } from "firebase-functions/params"
    import { printSecret } from "./handlers/secretTest"
    const secretTest = defineSecret("TEST_NOT_REALLY_SECRET")
    
    const app = express()
    
    app.use(
      cors({
        origin: function (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) {
          // allow requests with no origin
          // (like mobile apps or curl requests)
          if (!origin) return callback(null, true)
          // return the callers origin - Access-Control-Allow-Origin: <origin>
          // Note for testing locally: Some browsers may reject "localhost" as origin.
          return callback(null, true)
        },
      }),
    )
    
    app.get("/test", printSecret)
    
    export const api = onRequest({ region: "europe-west1", secrets: [secretTest] }, app)
    

  2. You’re going to have to write some code to pass along the value, and be a bit clever about it. The system isn’t going to do it for you. I’ll give you the general shape of a solution, and it’ll be up to you to fill in the implementation details.

    The values for secrets are only available at function runtime. Or, in other words, when your express app is handling a request. So your route handler needs to access the secret value in order to use it.

    This is where you are defining the route for the handler function handleNewProfile:

    app.post("/profiles/", handleNewProfile)
    

    Instead of using handleNewProfile directly, whose implementation doesn’t know anything about the secret, you will have to return a function that can find the value and inject it into handleNewProfile.

    What you can do here is create an intermediate handler implementation that:

    1. Accept the secret value as input
    2. Invokes the actual handler function that now has knowledge of the secret value.

    So it will look something like this (this will certainly not work exactly as-is – you will have to fix it up to work the way you want):

    const mySecret = defineSecret('YOUR_SECRET');
    
    app.post("/profiles/", (req, res) => {
        const secretValue = mySecret.value()
        handleNewProfile(req, res, secretValue)
    })
    
    // You will need to change the implementation of handleNewProfile
    // in order to accept this new parameter.
    //
    function handleNewProfile(req, res, secretValue) {
        // do the work using secretValue
    }
    

    Alternatively, you could implement a factory function that returns the actual handler function that’s had the secret value passed along to it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search