How to secure API Keys

Hi there,
I have seen some posts here regarding this question, but none that really gives a good answer or conclusion, as to how to best handle API keys.

I am facing this challenge now in a new app I am developing, and from what I have read, the only really safe way is to have a server backend/proxy where you keep the API Keys, but that is something I do not have and have no experience with setting up at all. I know there are services out there that can be used, but to my knowledge, these requires skills I do not have in terms of setting it up.
Anyone out there with experience on this and know how and/or have used any backend/proxy services out there they would recommend?

Hi Finn,

Welcome to the community.

I’m assuming that you have only recently joined CWC+. There is a Networking course that Chris Ching has and in that he describes a method to secure your API key by storing it in a Config.xcconfig file within your Xcode project which you then add to .gitignore so that you avoid exposing it to your remote GitHub repo if you have one.

Documentation on the file format of the Config.xcconfig file can be found at this link:

https://help.apple.com/xcode/mac/11.4/#/dev745c5c974

Essentially you create a Config.xcconfig file in Xcode by tapping File > New > File and selecting Configuration Settings File in the “Other” group.

In that file, add a line:

API_KEY = yourAPIKeyString (no quotes required)

and save it.

In your project you can create a Swift file to store all your Constants. Let’s assume that you are accessing a Yelp API just as an example. This is what mine looks like:

struct Constants {
    static let API_ENDPOINT = "https://api.yelp.com/v3/businesses/search"
    static var DETAILS_URL = "https://api.yelp.com/v3/businesses/"
    static let API_KEY = Bundle.main.infoDictionary?["API_KEY"] as? String
}

So that’s how you reference the API_KEY that is stored in your Config.xcconfig file.

Hi Chris,
Thank you for your reply!
However, as far as I can read in this article: Secret Management on iOS - NSHipster it is not really considered to be secure to do it in a config file either, hence the section Store Secrets in Xcode Configuration and Info.plist in that article.

I agree, read that all config file anyway can be seen as it is stored in apk.
Strugling with same issue and can’t find any tutorial expalanation how to store API in some db and how to call it, use it. By using Firebase or Supabase or maybe some other service which could be free anyways as you only store api key nothing else.

If your app needs to use an API Key from the front-end, you need to protect it from mainly two types of attacks:

  • hackers that will pry the app open or try to find it on the memory disk

  • hackers that will intercept network requests to get the key – (this hack is scary because you can learn how to do it in 10min on YouTube)

One way you can protect yourself is to never store the key on disk nor in the binary. Always make the app fetch it at launch from a secure server. Use Apple App Check to ensure the request is sent from a legit version of your app and not a cracked iPhone.

After that, you must protect all your network requests with SSL pinning.

but of course, the best security is to not have put your API key on the front-end :slight_smile: and use a server as proxy

But surely that does not apply to production apps, right? This is only for me playing with my own apps.

I watched the Networking Course, but where are the instructions for setting up the API key(s) in a secure way for a production app? Gemini taught me that I need to set up a server. It would be great if there was a video (or at least a solid forum entry) how to do that. Including things to look out for, recommendations for trustworthy services, and ideally step by step set up instructions.

Pretty much all the apps I am working on or plan to work in will need to be able to connect to other services and I really want to avoid for users to bring their own API key in most cases.

Chris Ching provided an option to “hide” the API string but as you have seen above there are other views on what to use in order to make that API string more secure. You might consider searching on forums such as stackoverflow for a method that suits your needs.

Hi, I have figured it out myself and implemented the following. In this case it was about securing the API keys for sending audio recordings for transcription to AssemblyAI. But it can be applied in a similar way for all kind of connections. Hope this helps people who are looking for a solution.

Just for reference, I am very experienced product manager and a very inexperienced developer, so proceed at your own risk. This approach was developed in a long conversation with Gemini 2.5 pro. I successfully implemented this.

Securing Third-Party API Keys: A Roadmap Using Supabase as a Proxy (Example: AssemblyAI)

Directly embedding third-party API keys (like AssemblyAI’s) into a mobile app is a significant security risk. If the app is decompiled, the key can be extracted and misused. A common solution is to use a backend proxy that securely stores the API key and handles communication with the third-party service on behalf of the app. Here’s how we achieved this using Supabase:

Overall Goal: The iOS app should never contain or directly use the AssemblyAI API key. All requests to AssemblyAI will go through Supabase Edge Functions, which use the securely stored API key.


Phase 1: Backend Setup (Supabase)

  1. Store Your Secret API Key in Supabase:

    • Why: To keep the AssemblyAI API key off the client device.
    • How: In your Supabase project dashboard, go to “Project Settings” > “Secrets” and add your AssemblyAI API key as a new secret (e.g., named ASSEMBLYAI_API_KEY). Your Edge Functions can then access this securely as an environment variable.
  2. Create an Edge Function for Job Submission (e.g., submit-transcription):

    • Why: This function will receive an audio file URL from your app, add the secret API key, and submit the transcription job to AssemblyAI.
    • Logic:
      • Input: Expects a POST request from your app with a JSON body containing {"audio_url": "URL_TO_YOUR_AUDIO_FILE"}.
      • Secrets: Retrieves the ASSEMBLYAI_API_KEY from its environment variables (Supabase secrets).
      • Action: Makes a POST request to the AssemblyAI transcription endpoint (e.g., https://api.assemblyai.com/v2/transcript). The request body to AssemblyAI includes the audio_url and any other parameters like language_detection: true. The Authorization header uses your secret AssemblyAI API key.
      • Output: Returns the initial JSON response from AssemblyAI (which includes the transcript_id and initial status like “queued”) back to your app.
    • Auth: Your app will call this function using its Supabase Anon Key (as a Bearer token in the Authorization header).
  3. Create an Edge Function for Polling Status/Results (e.g., get-transcription-status):

    • Why: AssemblyAI transcription is asynchronous. After submitting a job, your app needs to periodically check its status. This function proxies those status checks.
    • Logic:
      • Input: Expects a POST request from your app with a JSON body containing {"transcript_id": "THE_ID_FROM_SUBMISSION"}.
      • Secrets: Retrieves the ASSEMBLYAI_API_KEY from environment variables.
      • Action: Makes a GET request to the AssemblyAI transcript status endpoint (e.g., https://api.assemblyai.com/v2/transcript/{transcript_id}), including the secret API key in the Authorization header.
      • Output: Returns the full JSON response from AssemblyAI (containing the current status, the text of the transcript if completed, or an error message if failed) back to your app.
    • Auth: Called by your app using its Supabase Anon Key.
  4. Set Up Cloud Storage (e.g., Supabase Storage):

    • Why: AssemblyAI needs to access the audio file via a URL.
    • How: Create a bucket (e.g., audio-recordings) in Supabase Storage.
    • Permissions (RLS): Configure Row Level Security (RLS) policies on the storage.objects table for this bucket to allow your app (using its Anon Key) to upload (INSERT) files. Also, ensure the files are accessible via a public URL (or signed URL) for AssemblyAI to fetch.

Phase 2: iOS App Logic Changes

  1. Remove API Key: Delete the AssemblyAI API key from your app’s codebase, configuration files, and Keychain.
  2. Audio File Upload (Client-Side):
    • Why: Before submitting for transcription, the audio file must be accessible via a URL.
    • How: After recording, your app uploads the audio file from local device storage to the Supabase Storage bucket you created.
    • Upon successful upload, it gets a public URL for the file from Supabase Storage.
  3. Call “Submit Job” Edge Function (Client-Side):
    • How: Instead of calling AssemblyAI directly, your app’s networking service (e.g., your AssemblyAIService.swift) now makes an HTTPS POST request to your submit-transcription Supabase Edge Function URL.
    • It sends the Supabase Storage audio_url in the JSON body.
    • It receives the transcript_id and initial status from the function.
  4. Implement Polling Loop (Client-Side):
    • How: Using the received transcript_id, your app (e.g., in TranscriptionHandlerViewModel.swift) enters a loop:
      • Call your get-transcription-status Supabase Edge Function, sending the transcript_id.
      • Check the status in the response.
      • If “queued” or “processing”, wait a few seconds and poll again.
      • If “completed”, extract the transcription text.
      • If “error”, extract the error message.
      • Update local data (SwiftData) and UI accordingly.

Benefits:

  • Security: Your AssemblyAI API key is never exposed in the client app.
  • Control: You can add more logic in your Edge Functions later (e.g., request validation, rate limiting, custom logging) without app updates.
  • Scalability: Edge Functions are serverless and scale automatically.

This approach moves the sensitive operations to a controlled backend environment (Supabase), which your app communicates with, significantly enhancing the security of your AssemblyAI integration.