How to Secure API Calls in Your Mobile App (Without Exposing Your API Keys)


When developing a mobile app that interacts with an external API - whether it's OpenAI's ChatGPT, a third-party analytics service, or your own backend - the question of how to securely store API keys inevitably arises.

The common mistake many developers make is assuming they can hide an API key inside their app.

However, as security researchers have demonstrated time and again, if a mobile app contains an API key, it can be extracted.

The goal of this article is to provide a detailed breakdown of API security in mobile apps. We will examine:

  • The difference between authenticated and unauthenticated API calls and how each impacts security
  • The most common (and insecure) ways developers store API keys, and why they fail
  • The proper way to secure API requests, which involves using a backend proxy
  • Real-world examples, including insights from NSHipster, Jacob's Tech Tavern, and Hyperplexed

By the end, you will have a clear understanding of why API keys should never be included in a mobile app and how to design a backend that ensures security while preventing abuse.

API Calls With vs. Without Authentication

Before discussing API security, it is important to differentiate between API calls that require authentication and those that do not. The level of security required will depend on whether the API calls are tied to a specific user account.

Authenticated API Calls

When an API requires users to log in before making requests, security measures can be tied to user authentication. For example:

  • Each user can have an OAuth token, JWT (JSON Web Token), or session token that proves their identity
  • The backend can track user requests and enforce rate limits per user
  • If an account is compromised or misused, access can be revoked without affecting other users

This approach makes securing API keys much easier because users authenticate with credentials, and the app only needs to send the user's token to the server. The backend then verifies the token and makes API requests on behalf of the user.

Unauthenticated API Calls

When an API does not require authentication - such as a public endpoint for fetching data - security becomes significantly more challenging. In these cases:

  • Anyone can call the API, including bots or attackers
  • Rate limiting is harder because there is no user identity to track
  • API keys are at greater risk if exposed, as they can be used by anyone

For example, imagine an app that allows users to query OpenAI's ChatGPT without requiring a login.

If the app directly calls the OpenAI API, the API key must be stored somewhere in the app, which means it can be extracted by an attacker. This is why unauthenticated API calls require additional security measures, such as using a backend proxy to handle requests.

Common (Insecure) Ways Developers Store API Keys

Many developers try to hide API keys inside their app, but no method is truly secure if the key is embedded in the client. Let's examine the most common mistakes.

Hardcoding API Keys in Source Code

A common mistake is embedding API keys directly into the source code:

let apiKey = "12345678-90ab-cdef-1234-567890abcdef"
request.addValue(apiKey, forHTTPHeaderField: "Authorization")

While this may seem convenient, attackers can extract the key by decompiling the app. NSHipster explains how tools like Radare2 can analyse the compiled binary and extract hardcoded secrets. This means that even if an API key is not visible in the source code, it can still be found in the compiled app.

Using a reverse-engineering tool like Radare2, that secret is plainly visible from inside the compiled executable. NSHipster - Secret Management on iOS

Storing API Keys in `Info.plist` or `.xcconfig` Files

Some developers move API keys to Info.plist or Xcode configuration files:

let apiKey = Bundle.main.object(forInfoDictionaryKey: "API_KEY") as? String

However, since the Info.plist file is bundled with the app, an attacker can extract it from the .ipa file and read the API key in plaintext.

Regardless of how they find their way into Info.plist, these values are publicly available. Jacob Bartlett - How I Stole Your ChatGPT API Keys

Obfuscating API Keys

Some developers attempt to encrypt or obfuscate API keys to make them harder to extract. However, obfuscation is not a real security measure, because:

  • The app still needs to decrypt the key at runtime, making it accessible
  • Attackers can use tools like Frida to inspect the app's memory and retrieve the key

Even with a decent algorithm and salt, attackers can use tools like Frida to extract your API keys from device memory at runtime. Jacob Bartlett

The Proper Way to Secure API Calls: Use a Backend Proxy

Instead of storing API keys in the mobile app, the best practice is to proxy all API requests through a secure backend server. Here's how it works:

1. The Mobile App Sends Requests to Your Backend

  • Instead of calling the OpenAI API directly, the app sends a request to your backend
  • The request contains only the necessary parameters, without exposing any API keys

2. The Backend Calls the Third-Party API

  • Your backend securely stores the API key and makes the actual request to the third-party API
  • The response is then sent back to the mobile app

3. Implement Rate Limiting and Security Measures

  • The backend can track and limit API requests per user, per IP, or per session
  • Abuse prevention measures, such as CAPTCHAs or anonymous authentication, can be applied
  • If an API key is compromised, it can be rotated without requiring a client update

Real-World Example: Hyperplexed's Rate-Limited Counter

One of the most interesting real-world applications of rate limiting and abuse prevention comes from a Hyperplexed video, where the creator built a public counter that users could increment. The twist? If the counter reached one million, the entire site would be deleted.

While this was designed as a social experiment, it highlighted some very real challenges that developers face when handling large-scale public interactions with an API.

In particular, it raised the question of how to handle high volumes of traffic and prevent abuse without requiring users to log in.

How the System Worked

  1. Users could press a button to increase the counter
  2. No authentication was required, meaning anyone could participate
  3. The backend stored the counter value in Redis, a high-speed in-memory database
  4. Each button click triggered an API request to update the counter
  5. The counter updated in real-time, displaying the latest value to all users

At its core, this seemed simple. However, allowing unlimited, unauthenticated API calls led to potential issues with abuse.

Challenges: The Reality of Public APIs

Whenever an API is publicly accessible - whether in a game, a chatbot, or an app like this counter - you open yourself up to bad actors. Some key problems the Hyperplexed project faced (which are common in API-driven apps) included:

1. Spamming and Bot Attacks

Without limits, a malicious user could write a simple script to automate clicking the button, sending thousands of API requests per second. This would skew the results and potentially crash the server.

2. Denial-of-Service (DoS) Attacks

If too many requests hit the server at once, the database could become overloaded, making the site unresponsive for legitimate users.

3. Lack of User Authentication

Since anyone could interact with the counter, there was no way to distinguish between real users and automated bots.

4. Client-Side Rate Limits Are Not Enough

Even if the front-end limited the rate of clicks, bad actors could bypass this by sending direct HTTP requests to the API.

How Hyperplexed Solved These Issues

The developer implemented several smart backend protections to prevent abuse and ensure the counter worked as intended:

Anonymous Authentication to Identify Users

Instead of forcing users to create accounts, the system used anonymous authentication. This approach allowed the backend to assign a unique identifier to each user without requiring personal information.

  • Each visitor was given a temporary, unique user ID when they landed on the site
  • This ID was included in every API request they made
  • If a request did not include a valid ID, it was rejected

This was an important balance between security and user experience-users remained anonymous but could still be tracked for rate-limiting purposes.

Backend Rate Limiting to Prevent Spamming

The biggest vulnerability in this system was that anyone could send requests rapidly. The solution was to enforce rate limits on the backend using Redis.

  • A rate limit of two clicks per second per user was enforced
  • If a user tried to exceed this, their requests were ignored
  • The system used Redis expiration times to track each user's last click

Why Redis?

Redis is ideal for this because it operates in-memory and is extremely fast, meaning it can track user requests with minimal performance overhead.

# Example Implementation of Redis-Based Rate Limiting (Pseudocode):
def handle_click(user_id):
    if redis.get(user_id):  # Check if the user has clicked recently
        return "Rate limit exceeded"
    redis.set(user_id, "clicked", ex=0.5)  # Set a half-second cooldown
    increment_counter()

This ensures that no user can send more than two requests per second.

Server-Side Validation to Block Tampering

Since users controlled the client-side code, a malicious user could try modifying API requests before they were sent. The backend ensured that:

  • Requests had to match an active session ID
  • Only valid requests could increase the counter
  • Any request that looked suspicious was immediately blocked

Real-Time Updates Without Overloading the Server

Another challenge was keeping all users updated on the current counter value without flooding the database with requests. Instead of every user constantly polling the server, the developer:

  • Used real-time database broadcasting to push updates to all users only when necessary
  • Rate-limited how often the counter updates were broadcast (once per second)
  • This drastically reduced unnecessary API traffic, improving performance

Lessons for API Security and Real-World Apps

While Hyperplexed's project was a fun experiment, it offers valuable insights for real-world API security. If you are building an app that allows public API interactions, here are key takeaways:

Never Trust the Client

  • Any API request can be modified, automated, or abused
  • Client-side limits are meaningless - all restrictions must be enforced on the server

Always Implement Rate Limiting

  • Public APIs must have rate limits to prevent bot spam and server overload
  • Redis is a great choice for fast, scalable rate limiting

Use Anonymous Authentication to Track Users

  • Even if your API doesn't require logins, assigning temporary user IDs can help enforce per-user rate limits
  • Anonymous authentication provides a balance between privacy and security

Optimise Real-Time API Responses

  • Instead of every client constantly polling, use real-time updates that only trigger when necessary
  • This reduces API load and improves performance

Conclusion: Designing Smarter APIs

Hyperplexed's counter project is a great example of how public APIs can be abused - and how to protect them. While this project focused on a simple button-clicking game, the same security principles apply to real-world applications.

If you are building an API-driven mobile app, always assume that:

  • Users will try to bypass restrictions
  • Bad actors will try to automate API requests
  • If an API key is exposed, it will be stolen
  • Rate limiting, authentication, and server-side validation are essential

By offloading API calls to a secure backend, implementing rate limits, and tracking user activity, you can prevent abuse, ensure fair usage, and keep your app secure.


Enjoyed this content? Fuel my creativity!

If you found this article helpful, you can keep the ideas flowing by supporting me. Buy me a coffee or check out my apps to help me create more content like this!

Coffee Check out my apps