Секретный API-ключ в приложении Expo: что защищать
Автор: Mehdi Azizi
Обновлено , 7 мин
Публичный ключ Supabase, серверный секрет, переменные Expo: разбираем, что можно класть в iPhone-приложение, и проверяем доступ двумя аккаунтами.
Переменная окружения не всегда секрет
В приложении Expo переменная с префиксом EXPO_PUBLIC_ предназначена для кода приложения. Серверного секрета в ней быть не должно. Expo объясняет, что эти значения встраиваются в скомпилированный JavaScript и пользователи приложения могут их прочитать. Так что перенести ключ в файл .env недостаточно, чтобы его защитить. Источник: переменные окружения Expo.
Прежде чем просить ИИ «подключить API», разложи по полочкам, что ты ему доверяешь: адрес сервиса, ключ, который задуман публичным, или ключ, дающий право на привилегированные действия. От этого зависит, где должен выполняться код.
Таблица ниже это схема для проектирования, а не аудит твоего приложения.
| Элемент | Где хранить | Что проверить |
|---|---|---|
| Публичный адрес бэкенда | Конфигурация приложения | Он указывает на нужное окружение. |
| Ключ Supabase publishable или старый ключ anon | В приложении, если права доступа настроены правильно | Правила доступа к данным реально не пускают в аккаунт другого пользователя. |
| Ключ Supabase secret или service_role | Только на сервере | Его нет ни в мобильном бандле, ни в логах, доступных клиенту. |
| Секретный ключ ИИ-провайдера | Только на сервере | Приложение обращается к твоему серверу, а тот проверяет пользователя и лимиты до запроса к провайдеру. |
Supabase разделяет ключи для клиентов и секретные ключи для доверенных окружений. Публичный ключ идентифицирует проект, но не заменяет права доступа к данным. Источник: API-ключи Supabase.
Пример: вымышленное приложение-дневник чтения
Представим приложение, в котором Лея и Сами записывают свои книги и личные заметки. Этот пример нужен, чтобы подготовить тесты. Он не описывает результаты, полученные на приложении Devoria.
Обычный сценарий выглядит нормально: Лея входит и видит свои заметки. Но главный вопрос пока без ответа: отклонит ли сервер запрос Леи к заметке Сами?
Фильтр в интерфейсе вроде «показывать заметки вошедшего пользователя» улучшает отображение. Но авторизация должна применяться и на уровне данных. В Supabase правила Row Level Security позволяют ограничить доступные строки в зависимости от сессии. Supabase рекомендует включать их для таблиц, открытых через API, и объясняет политики чтения и записи. Источник: Row Level Security.
В своём тестовом проекте привяжи каждую заметку к владельцу и отдельно определи, кто может её читать, создавать, изменять и удалять. Файлы тоже проверь: защищённая строка в базе ещё не доказывает, что картинка приватна.
Сценарий проверки с двумя тестовыми аккаунтами
Подготовь два аккаунта под твоим контролем, в каждом свою книгу и небольшую узнаваемую заметку. Используй вымышленные данные в разрешённом тестовом окружении.
- Прочитать свою заметку. Аккаунт A находит заметку A.
- Прочитать заметку B из аккаунта A. Запрос не раскрывает её приватное содержимое.
- Изменить заметку B из аккаунта A. Сервер отказывает, заметка B не меняется.
- Создать запись от имени B из аккаунта A. Сервер отклоняет такую привязку.
- Прочитать без сессии. Никакой приватный контент не возвращается.
- Открыть приватный файл B из аккаунта A. Файл недоступен.
Записывай фактический результат, проверенную версию и ошибку, если она была. «Правила включены» это не результат теста. Чек-лист проверки из раздела ресурсов можно взять за основу, чтобы сохранять эти доказательства.
Платные запросы через твой сервер
Для функции, которая делает краткое содержание книги через ИИ-API, цепочка может быть такой: приложение → твой сервер → провайдер → твой сервер → приложение. Секретный ключ остаётся на сервере. Но одной этой архитектуры мало: доступ к серверу тоже нужно контролировать.
В вымышленном примере сервер должен, в частности, проверять сессию, проверять, что книга принадлежит этому человеку, ограничивать повторные запросы и возвращать понятную ошибку, если провайдер не отвечает. Клиент сам не решает, что у пользователя есть платный доступ.
Подготовь четыре проверки: пользователь не вошёл, книга чужого аккаунта, лимит исчерпан и провайдер недоступен. Последняя позволяет убедиться, что приложение выходит из состояния загрузки и не теряет введённый текст.
Что можно попросить у ИИ-ассистента для кода
Вот задание на ревью, которое можно адаптировать под свой репозиторий. Не вставляй в него свои секретные значения:
Перечисли переменные, которые используют мобильный код и сервер, показывая только их имена. Найди привилегированные вызовы на стороне клиента. Для приватных данных опиши проверки чтения и записи, затем предложи сценарий тестирования с двумя тестовыми аккаунтами. Не выполняй ротацию ключей и не меняй продакшен без подтверждения.
Читай ответ вместе с соответствующим кодом. Ассистент может заметить несоответствие, но его ответ не заменяет запуск тестов. В гайде как создать приложение с Claude Code объясняется, как разбить эту работу на части.
Прежде чем развивать приложение дальше
Ты должен уметь просто объяснить три вещи: какие значения публичны, какие операции идут через сервер и какие тесты показывают, что один аккаунт не видит данные другого. Если хоть что-то из этого неясно, разберись до того, как добавлять новые функции.
Страница создать приложение для iPhone показывает место этого этапа во всём проекте. Чтобы двигаться по порядку, в iOS Kit есть путь из четырёх этапов и стартовый код, где уже есть аккаунты и ИИ. Если не понимаешь, с какого этапа продолжить, бесплатная диагностика расставит твой проект по порядку и пришлёт мой чек-лист против отказов Apple.
Частые вопросы
Где хранить секретный API-ключ в приложении Expo?
На своём сервере, никогда не в приложении. Ключ Supabase secret или service_role, как и секретный ключ ИИ-провайдера, должен оставаться только на сервере. Приложение обращается к твоему серверу, а тот проверяет пользователя и лимиты, прежде чем вызвать провайдера. Убедись, что ни одного из этих ключей нет ни в мобильном бандле, ни в логах, доступных клиенту.
Защищает ли файл .env мой API-ключ в Expo?
Сам по себе нет. Переменная с префиксом EXPO_PUBLIC_ предназначена для кода приложения, и серверного секрета в ней быть не должно. Expo объясняет, что эти значения встраиваются в скомпилированный JavaScript и пользователи приложения могут их прочитать. Так что перенести ключ в файл .env недостаточно, чтобы его защитить.
Чем публичный API-ключ отличается от секретного?
Публичный ключ задуман для поставки внутри приложения: он идентифицирует проект, но не заменяет права доступа к данным. Секретный ключ даёт право на привилегированные действия и предназначен только для доверенных окружений. В Supabase ключ publishable или старый ключ anon может оставаться в приложении, если права доступа настроены правильно. Ключи secret и service_role остаются на сервере.
Как вызывать ИИ-API из приложения Expo, не раскрывая ключ?
Пропускай вызов через свой сервер: приложение обращается к серверу, тот вызывает провайдера и возвращает ответ приложению. Секретный ключ остаётся на сервере. Сервер также должен проверять сессию, ограничивать повторные запросы и возвращать понятную ошибку, если провайдер не отвечает. Клиент сам не решает, что у пользователя есть платный доступ.
Как проверить, что каждый пользователь видит только свои данные?
Подготовь два тестовых аккаунта под своим контролем, в каждом по узнаваемой заметке. Из аккаунта A попробуй прочитать, изменить и создать запись от имени аккаунта B, а затем открыть приватный файл B. Сделай и проверки без сессии: каждый запрос должен быть отклонён или ничего не раскрывать. Записывай фактический результат, потому что «правила включены» это не результат теста.
Как попросить ИИ проверить ключи в моём приложении?
Попроси его перечислить переменные мобильного кода и сервера, показывая только их имена, и не вставляй свои секретные значения. Попроси также найти привилегированные вызовы на стороне клиента и предложить сценарий тестирования с двумя тестовыми аккаунтами. Запрети любую ротацию ключей и изменения в продакшене без подтверждения. Его ответ не заменяет запуск тестов.