
react native expo bare workflow 환경에서 앱을 배포하기 위해 우선 환경변수를 설정하기
환경변수를 어떻게 처리해야하나 고민하다가 expo 공식 블로그에서 너무 좋은 글을 찾았다.
https://expo.dev/blog/what-are-environment-variables
이 글에선 우선 secret한 정보란 무엇인지 정의한다.
Is it safe to add secrets to environment variables? Let us first establish what a “secret” is: a secret, in this context, is anything that you wouldn’t be comfortable with anyone else having access to.
For example, your OpenAI apiKey would be a secret, because it is a service where you had to create an account and you pay per request. So any malicious actors using your apiKey would use up your usage limits. Any credentials, private keys or passwords such as ‘database connection strings’ are also secrets.
...
In summary: never store secrets in environment variables that are used in client side code because any code you add to a website or a mobile app (even if it’s added using an environment variable) can be accessed in plain text.
글에 따르면 누군가 그 정보에 접근하는게 불편하다면 그게 secret한 것이라고 한다.
이 관점에서 보자면 내 상황에서는 글에 나온 예처럼 openAI API Key 그리고 Sentry Auth Token이다. 그런데 여기서 Sentry Auth Token는 EAS Secrets를 통해 올리면 된다고 한다.

openAI API Key도 EAS Secrets에 올리면 되는거 아닐까 생각을 했다.

그런데 EAS Secrets 문서를 읽어보니 위 expo 글처럼 클라이언트 사이드 코드에 들어간 내용은 공개된 것이라고 생각하라고 하며 EAS Secrets도 결국 클라이언트 사이드에 들어가는 내용은 추가적인 보안을 제공하지 못한다는 점을 강조하고 있었다.
Sentry Auth Token은 빌드할 때만 필요한 값이기 때문에 EAS Secrets를 통해 올리라고 한 것 같다.
그렇다면 앱 실행 중에 필요한 값인 openAI API Key는 어떻게 해야할지 고민하다가 레딧에서 한 글을 발견했는데 앱이 실행 중일 때만 API Key를 서버에서 받아서 쓰면 된다고 한다.
https://www.reddit.com/r/reactnative/comments/1amfq6p/api_keys_storage/
그러면 앱이 실행 중일 때 메모리에만 그 값이 있으니 클라이언트 코드에 값을 넣어 두는 것보다 더 안전한 것이다!
실행 중인 앱의 메모리를 뜯어 보는건 가능한가 찾아보니 메모리 스크래핑, 메모리 덤프 정도의 모바일 앱 취약점 분석과 관련된 키워드를 찾을 수 있었다. 🔗 🔗
하지만 당장 이것까지 더 깊이 파보고 대비하기에는 시간이 너무 많이 들 것 같다. 차차 해결해 나가야겠다.
그럼 이제 환경 변수를 디벨롭 환경과 프로덕션 환경으로 구분해보자!
우선 앞서 계속 보던 expo 블로그 글을 계속 보니
There are three ways to set environment variables in Expo projects:
.env files
eas.json
EAS Secrets
If you use EAS, you’ll usually end up with a combination of the three to ensure your environment variables are set in all cases.
.env, eas.json, EAS Secrets를 경우에 따라 선택해서 쓰게 될 것이라 한다!
인상깊었던 것은
With Expo, all environment variables that are used in the frontend code will have to be prefixed with EXPO_PUBLIC, so e.g. your API_URL would become EXPO_PUBLIC_API_URL. This is to make it absolutely clear that anything stored in these environment variables should not be secret as it will be used in the frontend.
클라이언트 사이드에 저장되는 것은 모두 공개되는 것과 다름 없으니 환경 변수이름 앞에 EXPO_PUBLIC이라는 접두사를 붙인다는 것이다.
그리고 이 환경 변수는 위에서 나온 방법 중
EAS cloud에서 빌드한다면 eas.json 안에서 설정할 수 있고 🔗
// 블로그 글의 예시
//eas.json
{
"build": {
"development": {
...,
"env": {
"EXPO_PUBLIC_API_URL": "http://localhost:3000"
}
},
"preview": {
...,
"env": {
"EXPO_PUBLIC_API_URL": "https://api.staging.app.com"
}
},
"production": {
...,
"env": {
"EXPO_PUBLIC_API_URL": "https://api.app.com"
}
}
},
}
로컬에서 빌드한다면 프로젝트 루트 디렉토리에 .env 파일 안에 넣어놓으면 된다고 한다. 🔗
// 블로그 글의 예시
//.env
EXPO_PUBLIC_API_URL=http://localhost:3000
그리고 이렇게 하드코딩하기 조금 민감한 정보라면(e.g 위에서의 Sentry Auth Token) EAS Secrets를 사용하라고 한다. 로컬에서라면 .env에 넣으라고 Sentry for React Native get started 문서에서 본 것 같다. 아니면 자동으로 파일이 생겼던가 그랬던거같다.
그런데 나는 여기서 좀 더 나아가서 내가 빌드하는 옵션에 따라서 프로덕션과 디벨롭에 필요한 환경변수가 바뀌었으면 한다. 그러다가 좋은 글을 발견했다.
위 글을 참고해서 app.config.js에서 APP_ENV 환경변수를 통해 변수를 동적으로 설정할 수 있게 하고, 빌드시 명령어에서 cross-env를 사용해 환경을 선택할 수 있게 했다. expo constants를 사용해 코드에 변수를 적용했다.
그리고 package.json에서 각 환경에 맞는 scripts를 등록해둬서 편리하게 환경을 구분해 실앱을 실행할 수 있게 했다.
참고한 블로그에 joi를 사용해 환경변수의 유효성을 검증하는 것도 있던데 나중에 적용해볼 생각이다.
내 프로젝트 폴더에선 app.config.js가 아니라 app.json이라 우선 파일 확장자부터 바꾸었다.
json에서 js로 바꾸었기 때문에 동적으로 환경변수를 설정할 수 있게 되었다.
//app.config.js
let Config = {
// 기본 값으로 개발 환경을 설정했다.
BASE_URL: 'https://10.0.0.2:8000',
SENTRY_MODE: 'development',
};
if (process.env.APP_MODE === 'production') {
Config.BASE_URL = 'https://우리서비스도메인';
Config.SENTRY_MODE = 'production';
} else if (process.env.APP_MODE === 'development') {
Config.BASE_URL = 'https://dev.우리서비스도메인';
}
// .export가 아니라 .exports다 이 문제는 아래 시행착오가 있다...
module.export = {
... 생략
extra: {
eas: {
// eas projectId는 내가 따로 설정한 기억이 없다.
// eas build 명령어를 실행하는 과정에서 자동으로 생긴 것 같다.
projectId: '내 프로젝트 id',
},
...Config,
},
... 생략
환경 변수들을 넣다가 보니 추가로 더 생각난 것들이 있다.
android client id : 구글 로그인 api 호출 시 인자로 들어간다. 그래서 앱 실행 중 필요한데 expo 문서를 보니 외부로 노출되면 안되기 때문에 서버에서 받아서 쓰라고 한다. 🔗
sentry dsn: 이건 앱 식별용 기능만 해서 노출되어도 괜찮다고 한다. 🔗
블로그에서 사용한 라이브러리를 설치하려고 보았다.
cross-env, expo-constants
cross-env가 뭔지 간단하게 찾아보다가 한 블로그를 보았는데 거기서 corss-env 라이브러리 관련 재밌는 이야기도 보았다. cross-env와 비슷한 이름을 활용한 crossenv라는 악성 패키지가 예전에 있었다고 한다. 나도 종종 npm에서 패키지 설치할 때 이름만 따라적곤 했는데 주의해야겠다.
그런데 cross-env는 현재 maintenance mode라고 한다. 마지막 업데이트가 2020년이다. cross-env를 사용하는 이유가 윈도우와 포식스 계열의 환경 변수 설정 호환을 위해서인데 우리 팀은 모두 맥을 사용해서 굳이 4년 전에 마지막으로 업데이트된 패키지를 써서 예상치 못한 문제를 겪고싶지 않다.
그래서 expo-constants만 설치하고 블로그를 따라 환경 변수를 관리하는 코드를 작성했다.
// /constants/env.js
import Constants from 'expo-constants';
const getEnv = key => Constants.expoConfig?.extra?.[key];
export const env = {
APP_MODE: getEnv('APP_MODE'),
BASE_URL: getEnv('BASE_URL'),
SENTRY_MODE: getEnv('SENTRY_MODE'),
};
그런데 궁금한 점이 생겼다.
이 코드는 app.config.js에서 extra 부분에서 설정되는 환경 변수를 가져오는 것 같은데 이걸 app.config.js 최상단에서 호출하면 어떻게 될까. 에러가 뜨지 않을까 나중에 확인해볼 것.
다음으로 프로젝트 코드 내부에서 env를 import한 뒤 설정한 환경 변수를 적용했다.
그리고 마지막으로 APP_MODE를 커멘드로 일일히 주지 않아도 되게 package.json에서 script를 수정했다.
//package.json
...
"scripts" : {
"start:dev": "APP_MODE=development expo start",
"start:prod": "APP_MODE=production expo start"
... 생략
}
그리고 확인차 루트 컴포넌트에서 env를 import해서 로그를 찍어보았는데
LOG env {"APP_MODE": undefined, "BASE_URL": undefined, "SENTRY_MODE": undefined}
값들이 전부 undefined로 나온다. 이런
우선 환경 변수가 제일 처음 정의되는 app.config.js에서 로그를 찍어보았다.
그랬더니 설정한 값대로 잘 나온다.
그럼 env.js에서는 값을 잘 받아오는지 로그를 찍어보았다.
그랬더니 여기서 undefined가 뜨고 있었다!
그렇다면
const getEnv = key => Constants.expoConfig?.extra?.[key];
이 코드가 문제일 가능성이 커보인다.
그래서 Constants 로그도 찍어보았는데 ExpoConfig 속성 안에 extra 안에 내가 설정한 값 자체가 없다.
GPT와 대화하니 bareflow에선 app.config.js에 설정한 값이 자동으로 반영이 안될 수 있다고 한다.
그래서 expo bareflow expo constants undefined 등 키워드로 검색해도 전혀 내 상황과 맞는 글이 없었다. 그래서 한시간 반동안 씨름하다가 app.config.js 파일 전체를 코파일럿한테 물어보니까...
x module.export = {
O module.exports = {
라고 한다...... 하하

vscode에서 왜이렇게 작게 표시가 되는 건지...
problmes에 전혀 나오지도 않고 저 ex부분 아래에 ...이게 다다

...으로 표시되는 경고는 ts에러였다.
ts로 넘어갈 이유가 하나 더 생겼다...
강력한 정적분석....
아무튼 이제 잘 된다... ㅠㅠ
최종 정리
| 변수이름 | 타입 | 위치 |
|---|---|---|
| openAI api key | string | 서버 |
| Sentry auth token | string | EAS Secrets |
| 우리 서비스 api url | string | app.config.js |
| sentryMode | production | development | config.js |
| android client id | string | 서버 |
| Sentry dsn | string | 파일 내부 |
아무튼 환경변수 설정을 하면서 적어도 민감한 정보가 쉽게 노출되지 않는 것에 집중해서 작업을 해보았다.
그외
앞으로 확인할 것

앞으로 찾아볼 것
버전 관리에 대해서