App Store Connect와 Google Play API 비교: 인증·권한·한계
앱 하나를 두 스토어에 등록하면서 양쪽 API를 같은 날 연달아 만졌습니다. 목적이 똑같았기 때문에 차이가 잘 보였습니다. 인증 방식부터 다르고, 권한을 다루는 사고방식이 다르고, 심지어 "안 되는 걸 알려주는 방식"까지 달랐습니다.
자격증명의 모양
Apple은 세 조각이 필요합니다.
- Key ID (10자리 식별자)
- Issuer ID (계정당 하나, UUID)
.p8개인키 파일
Google은 파일 하나입니다. 서비스 계정 JSON 안에 이메일과 개인키가 다 들어있습니다. 그래서 Apple 쪽은 세 값을 각각 어디서 가져올지 신경 써야 하는데, Google은 파일 경로 하나만 알면 끝납니다.
대신 이 구조 때문에 Google 쪽 JSON은 그 자체로 완전한 열쇠입니다. Apple은 Key ID와 Issuer ID가 유출돼도 .p8 이 없으면 아무것도 못 하지만, 서비스 계정 JSON은 파일 하나가 곧 계정입니다.
서명한 토큰을 바로 쓰느냐, 한 번 바꾸느냐
여기가 제일 헷갈렸던 부분입니다. 둘 다 "JWT를 직접 서명한다"까지는 같은데, 그 다음이 갈립니다.
Apple은 서명한 JWT를 그대로 Bearer 토큰으로 씁니다.
// ES256. Apple은 raw R||S 형식을 기대하므로 dsaEncoding 을 반드시 지정합니다.
const signature = crypto.sign('sha256', Buffer.from(signingInput), {
key: p8Pem,
dsaEncoding: 'ieee-p1363',
});
// 이 JWT 자체가 Authorization: Bearer 값Google은 서명한 JWT를 토큰 엔드포인트에서 액세스 토큰으로 교환한 뒤, 그 액세스 토큰을 씁니다. 단계가 하나 더 있습니다.
// RS256으로 서명한 뒤
POST https://oauth2.googleapis.com/token
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<JWT>
// 응답의 access_token 을 Authorization: Bearer 로 사용알고리즘도 다릅니다. Apple은 ES256(타원곡선), Google은 RS256(RSA)입니다. Apple 쪽에서 dsaEncoding: 'ieee-p1363' 을 빼먹으면 Node가 기본값인 DER로 서명해버려서 401이 나는데, 에러 메시지만 봐서는 서명 형식 문제인지 키 문제인지 구분이 안 됩니다. 저는 이걸 처음에 키가 잘못된 줄 알고 한참 봤습니다.
목록을 볼 수 있느냐
이건 API 설계 철학 자체가 다른 지점입니다.
Apple은 GET /v1/apps 로 앱 컬렉션을 통째로 조회할 수 있습니다. 내가 접근 가능한 앱이 뭔지 목록으로 확인됩니다.
Google Play Android Publisher API에는 앱 목록 엔드포인트가 없습니다. 모든 경로가 /applications/{packageName}/... 형태라 패키지명을 이미 알고 있어야 합니다. 그래서 "이 서비스 계정이 어떤 앱에 접근되는지" 확인하려면 패키지명을 하나씩 찔러보는 수밖에 없습니다.
저는 편집 세션 생성으로 확인했습니다.
POST /androidpublisher/v3/applications/{packageName}/edits
// 200이면 접근 가능, 404면 접근 불가패키지 세 개를 넣어서 하나씩 돌려봤습니다. Apple에서는 목록 한 번 부르면 끝날 일이었습니다.
404가 두 가지를 뜻합니다
접근 확인을 하다가 알게 된 차이입니다.
Apple은 못 하는 일을 알려줄 때 아주 구체적입니다.
{
"code": "FORBIDDEN_ERROR",
"detail": "The resource 'apps' does not allow 'CREATE'. Allowed operations are: GET_COLLECTION, GET_INSTANCE, UPDATE"
}무엇이 막혔고 대신 무엇이 되는지까지 적혀 있습니다. 이 메시지 덕분에 "권한을 올리면 되겠지"라는 제 가정이 틀렸다는 걸 비교적 빨리 알았습니다. 정확히는 키를 세 개나 만들어보고 나서였지만, 어쨌든 답은 응답 안에 있었습니다.
Google은 404 Package not found 하나로 두 상황을 덮습니다. 앱이 존재하지 않는 경우와, 존재하지만 이 서비스 계정에 권한이 없는 경우가 같은 응답입니다. 보안상 의도된 설계지만, 디버깅할 때는 "아직 안 만든 건가, 권한을 안 준 건가"를 응답만 보고는 판단할 수 없습니다.
권한을 붙이는 위치
Apple은 축이 둘입니다.
- 키 자체의 역할 (관리자, 앱 관리 등)
- 앱 쪽의 접근 범위 설정 (제한된 액세스 / 전체 액세스)
이 둘이 동시에 맞아야 키가 그 앱을 봅니다. 키에 "전체 앱" 권한이 있어도 앱이 "제한된 액세스"로 만들어졌으면 목록에서 조용히 빠집니다. 에러가 나는 게 아니라 그냥 안 보이기 때문에, 처음에는 반영이 늦는 줄 알고 몇 번 더 조회했습니다.
Google은 축이 하나입니다. 서비스 계정이 Play Console의 "사용자 및 권한" 목록에 사람 계정과 나란히 사용자처럼 등록됩니다. 여기서 계정 전체 권한을 주면 이후 만드는 앱은 자동으로 포함됩니다. 실제로 이번에 새 앱을 만들자마자 아무 설정 없이 바로 200이 떨어졌습니다.
개인적으로는 Google 쪽 모델이 이해하기 쉬웠습니다. 서비스 계정을 "팀원 한 명"으로 취급하니 권한을 어디서 확인해야 하는지 헷갈릴 일이 없습니다.
둘 다 못 하는 일
앱 생성입니다. 이건 양쪽이 똑같습니다.
Apple은 POST /v1/apps 가 403으로 막혀 있고, 역할을 관리자까지 올려도 마찬가지입니다. Google은 아예 해당 엔드포인트가 존재하지 않습니다. 결국 두 스토어 모두 앱 레코드 자체는 웹 콘솔에서 손으로 만들어야 합니다.
다만 그 앞 단계는 갈립니다. Apple은 Bundle ID 등록(POST /v1/bundleIds)이 API로 되기 때문에, 식별자를 미리 만들어두고 앱 생성만 웹에서 하는 식으로 나눌 수 있습니다. Google은 패키지명이 첫 AAB 업로드 시점에 결정되므로 미리 등록할 대상 자체가 없습니다.
파일 이름에 속았습니다
마지막은 API 차이가 아니라 제 착각입니다.
서비스 계정 키 파일 이름에 다른 프로젝트 이름이 들어 있어서, 저는 그게 그 프로젝트 전용 키인 줄 알았습니다. 그래서 "이 키를 새 앱에 재사용할 수 있나"를 한참 고민했습니다.
찔러보니 반대였습니다. 파일명에 적힌 그 프로젝트의 패키지는 404가 나고, 정작 이름과 무관한 쪽 패키지가 200이 나왔습니다. 키가 만들어진 Google Cloud 프로젝트와, 그 키가 연결된 Play 개발자 계정은 별개였던 겁니다. 파일 이름은 만들 때 붙인 라벨일 뿐 소속을 뜻하지 않았습니다.
이번 작업에서 계정을 착각한 게 이번이 두 번째였습니다. 앞서 Expo 쪽에서도 저장소 계정과 Expo 계정을 같은 것으로 착각했었습니다. 이름이 비슷하면 같은 소속이라고 넘겨짚는 버릇이 있는 것 같습니다.
정리
| Apple ASC API | Google Play API | |
|---|---|---|
| 자격증명 | Key ID + Issuer ID + .p8 |
서비스 계정 JSON 하나 |
| 서명 알고리즘 | ES256 (ieee-p1363 필수) |
RS256 |
| 토큰 | 서명한 JWT를 그대로 사용 | JWT를 액세스 토큰으로 교환 |
| 앱 목록 조회 | GET /v1/apps 로 가능 |
엔드포인트 없음, 패키지명 필요 |
| 권한 축 | 키 역할 + 앱 접근 범위 (둘 다 필요) | 서비스 계정을 사용자로 등록 |
| 접근 불가 응답 | 403 + 허용 동작 명시 | 404 (없음/권한없음 구분 불가) |
| 앱 생성 | 불가 (403) | 불가 (엔드포인트 없음) |
| 식별자 사전 등록 | Bundle ID API로 가능 | 첫 업로드 때 결정 |
자동화 가능한 범위는 결국 비슷합니다. 앱을 만드는 일은 사람이 웹에서 하고, 그 뒤의 릴리스와 제출은 기계가 합니다. 그 경계가 양쪽 다 같은 자리에 그어져 있다는 게 조금 의외였습니다.
함께 읽기
- Expo 앱 스토어 배포 준비 체크리스트: 순서가 중요한 이유새 Expo 앱 하나를 두 스토어에 올릴 준비를 했습니다. 필요한 항목 자체는 검색하면 다 나옵니다. 그런데 정작 저를 막은 건 항목이 아니라 순서였습니다. 앞 단계가 끝나야 뒤 단계의 값이 생기는 구간이 몇 군데 있고, 그걸 모르고 덤비면 "왜 이 필드가 비어 있지"에서 멈춥니다.
- App Store Connect API 키: Team Key로 할 수 있는 것과 없는 것새 앱을 App Store Connect에 등록하면서, 화면을 계속 왔다 갔다 하기 귀찮아서 API로 자동화할 수 있는 부분은 자동화해보려고 했습니다. 결론부터 적으면 절반만 성공했습니다. 조회·수정은 되는데, 앱 생성 자체는 API로 안 됩니다.
- RN 앱 두 번째 배포: 재사용되는 인증서, 새로 해야 하는 등록몇 주 전에 만든 고객 포털 앱을 반쯤 복사해서 새 RN 앱을 하나 더 만들었습니다. 성경 속 여정을 지도에 그리는 화면 하나를 웹에서 옮겨온 것뿐인데, 옮기고 나니 "인증서까지 그대로 가져다 쓸 수 있나"가 궁금해졌습니다. 결론부터 적으면 절반만 맞았습니다.
- Metro 포트 충돌로 배운 것: RN이 OTA 업데이트를 할 수 있는 이유새로 만든 앱을 아이폰에 설치했습니다. 빌드는 성공했고 설치도 됐는데, 앱을 열자 빨간 에러 화면이 떴습니다. 그런데 에러가 가리키는 파일이 이상했습니다.
- 같은 Expo 앱인데 배포 방식은 달랐습니다: iOS와 Android 심사 제출 비교기앞선 글에서는 이미 TestFlight에 올라간 iOS 빌드를 AI가 App Store 심사까지 제출한 과정을 다뤘습니다.