skip to Main Content

I have an app that uses OpenAI and like many others my key was recently compromised.

I have this simple code:

const functions = require('firebase-functions');
const OpenAI = require("openai");

const openai = new OpenAI({
    apiKey: functions.config().openai.key,
});

exports.generateText = functions.https.onCall(async (data, context) => {

  try {

    const response = await openai.chat.completions.create({
      messages: [{ role: 'user', content:  data.prompt }],
      model: 'gpt-3.5-turbo',
   });

   return { response: response.choices[0].message.content };

   } catch (error) {

    throw new functions.https.HttpsError('internal', 'Failed to generate text from OpenAI.');
  }
});

that I then call within my iOS app as follows:

let functions = Functions.functions()
func generateText(prompt: String, completion: @escaping (String?, Error?) -> Void) {
    
    functions.httpsCallable("generateText").call(["prompt": prompt]) { result, error in
        
        if let error = error as NSError? {
            if error.domain == FunctionsErrorDomain {
                let code = FunctionsErrorCode(rawValue: error.code)
                let message = error.localizedDescription
                let details = error.userInfo[FunctionsErrorDetailsKey]
                
                print(code, message, details)
                return
            }
        }
        
        if let textResponse = (result?.data as? [String: Any])?["response"] as? String {
            completion(textResponse, nil)
        } else {
            completion(nil, NSError(domain: "AppErrorDomain", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to parse function response"]))
        }
    }
}

However as I’m not a backend engineer, I’m wondering if this is secure, or what is to stop someone using this endpoint? E.g. is this automatically restricted to being called from my app? Is there a way to do that?

I note other responses ask to authenticate the user via a login, but I would rather avoid that within my app.

Thank you!

2

Answers


  1. From a Cloud Functions standpoint, that approach is fine. You have put your API key into the Configuration as an environment variable. It would be safer to put the key into Google Secret Manager, but your approach here is reasonable.

    Except….the question is how you are populating the Configuration for your environment variables and how you are storing the "source" of that information.

    For example, if you put the API key into some type of shell script and are passing that API key via firebase functions:config:set .... then it is the data in the shell script that is a risk. You need to protect that file. Storing it as clear text in your version control system, for example, would be an insecure approach.

    Login or Signup to reply.
  2. SecretManager is the most safe solution solution.

    https://firebase.google.com/docs/functions/config-env?gen=2nd

    https://codewithandrea.com/articles/api-keys-2ndgen-cloud-functions-firebase/

    You are using “Callable” functions you can check the authorization easily.

    // Checking attribute.
    // Checking that the user is authenticated.
    if (!request.auth) {
      // Throwing an HttpsError so that the client gets the error details.
      throw new HttpsError("failed-precondition", "The function must be " +
          "called while authenticated.");
    }
    

    https://firebase.google.com/docs/functions/callable?gen=2nd

    You can also implement “App Check”

    https://firebase.google.com/docs/app-check

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