# Secret API key in an Expo app: what to protect

> Supabase public key, server secret, Expo variables: learn what can ship in your iPhone app and test access with two accounts.

Original page: [Secret API key in an Expo app: what to protect](https://devoria.fr/en/blog/secret-api-key-expo-app-what-to-protect)
Author: [Mehdi Azizi](https://devoria.fr/en/mehdi-azizi)
Published: 2026-09-29
Content last revised: 2026-10-09


## An environment variable isn't necessarily a secret

In an Expo app, a variable prefixed with `EXPO_PUBLIC_` is meant for the app's code. It must not contain a server secret. Expo explains that these values are embedded in the compiled JavaScript and can be read by your app's users. So moving a key into a `.env` file isn't enough to protect it. [Source: Expo environment variables](https://docs.expo.dev/guides/environment-variables/).

Before you ask an AI to "hook up the API," sort what you're handing it: a service address, a key designed to be public, or a key that authorizes privileged actions. That sorting decides where the code has to run.

The table below is a design grid, not an audit of your app.

| Item                                         | Where to put it                              | What to check                                                                            |
| -------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Public backend address                       | App configuration                            | It points to the right environment.                                                      |
| Supabase publishable key, or legacy anon key | App, if permissions are configured correctly | Data rules actually block access to another user's account.                              |
| Supabase secret or service_role key          | Server only                                  | It doesn't appear in the mobile bundle or in logs the client can access.                 |
| Secret key from an AI provider               | Server only                                  | The app calls your server, which checks the user and limits before calling the provider. |

Supabase separates keys meant for clients from secret keys meant for trusted environments. A public key identifies the project; it doesn't replace permissions on the data. [Source: Supabase API keys](https://supabase.com/docs/guides/getting-started/api-keys).

## Example: a fictional reading journal app

Picture an app where Léa and Sami log their books and private notes. This example is here to prepare the tests; it doesn't describe results from a Devoria app.

The normal flow looks fine: Léa signs in and sees her notes. But that doesn't answer the important question yet: does the server reject a request from Léa targeting one of Sami's notes?

A filter in the UI, like "show the signed-in user's notes," improves the display. Authorization also has to be enforced on the data side. In Supabase, Row Level Security rules let you restrict which rows are accessible based on the session. Supabase recommends enabling them for tables exposed through the API and explains read and write policies. [Source: Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security).

For your own test project, tie each note to its owner and define separately who can read, create, update and delete it. Check files too: protecting a database row doesn't prove an image is private.

## The test run to do with two test accounts

Set up two accounts you control, a different book in each, and a short, recognizable note. Use fake data in an authorized test environment.

1. **Read your own note.** Account A finds note A.
2. **Read B's note from A.** The request doesn't reveal its private content.
3. **Edit B's note from A.** The server refuses; note B stays unchanged.
4. **Create on behalf of B from A.** The server refuses that ownership.
5. **Read without a session.** No private content is returned.
6. **Open B's private file from A.** The file isn't accessible.

Write down the actual result, the version tested and any error. "The rules are enabled" is not a test result. The [test grid available in the resources](https://devoria.fr/en/resources) can serve as a base for keeping that evidence.

## Route paid calls through your server

For a feature that summarizes a book with [an AI API](https://devoria.fr/en/blog/guideline-5-1-2-third-party-ai-att-data-sharing), the flow can be: app → your server → provider → your server → app. The secret key stays on the server. That architecture isn't enough on its own: access to the server has to be controlled too.

In the fictional example, the server must, among other things, check the session, check that the book belongs to the person, rate-limit repeated requests and return an understandable error if the provider doesn't respond. The client doesn't decide on its own that a user has paid access.

Prepare four tests: signed-out user, book from another account, limit reached and provider unavailable. The last one lets you check that the app leaves its loading state without losing what the user typed.

## What you can ask your coding assistant

Here's a review prompt to adapt to your repo, without pasting your secret values into it:

> List the variables used by the mobile code and by the server, showing only their names. Identify privileged calls made on the client side. For private data, describe the read and write controls, then propose a test run with two test accounts. Don't rotate any key or change production without approval.

Read the answer alongside the matching code. An assistant can spot an inconsistency; its answer doesn't replace actually running the tests. The guide [build an app with Claude Code](https://devoria.fr/en/blog/build-app-claude-code-iphone-tutorial) explains how to break this work down.

## Before you keep building your app

You should be able to explain three things simply: which values are public, which operations go through the server and which tests show that one account can't see another's data. If any of these is still fuzzy, clear it up before adding features.

The page [build an iPhone app](https://devoria.fr/en/expertise/create-an-iphone-app) puts this step in the context of the full project. To move forward in order, the [iOS Kit](https://devoria.fr/en/kits/ios) offers a four-step path and starter code that already includes accounts and AI. If you're not sure which step to pick back up from, the [free diagnostic](https://devoria.fr/en/diagnostic) puts your project back in order and sends you my Apple anti-rejection checklist.

## Frequently asked questions

### Where should I put a secret API key in an Expo app?

On your server, never in the app. The Supabase secret or service_role key, like an AI provider's secret key, must stay server-side only. The app calls your server, which checks the user and limits before calling the provider. Make sure none of these keys show up in the mobile bundle or in logs the client can access.

### Does a .env file protect my API key in Expo?

Not on its own. A variable prefixed with `EXPO_PUBLIC_` is meant for the app's code and must not contain a server secret. Expo explains that these values are embedded in the compiled JavaScript and can be read by your app's users. So moving a key into a `.env` file isn't enough to protect it.

### What's the difference between a public API key and a secret key?

A public key is designed to ship in the app: it identifies the project, but it doesn't replace permissions on the data. A secret key authorizes privileged actions and is reserved for trusted environments. With Supabase, the publishable key, or the legacy anon key, can stay in the app if permissions are configured correctly. The secret and service_role keys stay on the server.

### How do I call an AI API from an Expo app without exposing the key?

Route the call through your server: the app calls the server, which calls the provider and then sends the response back to the app. The secret key stays on the server. The server also has to check the session, rate-limit repeated requests and return an understandable error if the provider doesn't respond. The client doesn't decide on its own that a user has paid access.

### How do I check that each user only sees their own data?

Set up two test accounts you control, with a recognizable note in each. From account A, try to read, edit and create on behalf of account B, then open B's private file. Run the tests without a session too: every request must be refused or reveal nothing. Write down the actual result, because "the rules are enabled" is not a test result.

### How do I ask an AI to review my app's keys?

Ask it to list the variables in the mobile code and the server, showing only their names, without pasting your secret values. Also ask it to identify privileged calls made on the client side and to propose a test run with two test accounts. Forbid any key rotation or production change without approval. Its answer doesn't replace actually running the tests.


## Your project

[iOS Kit](https://devoria.fr/en/kits/ios): Build your iPhone app with Claude Code: the starter code behind my apps, skills that check its work, and the Apple Review, Screenshots and ASO kits. 129,99 €.
