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로 가능 | 첫 업로드 때 결정 |
자동화 가능한 범위는 결국 비슷합니다. 앱을 만드는 일은 사람이 웹에서 하고, 그 뒤의 릴리스와 제출은 기계가 합니다. 그 경계가 양쪽 다 같은 자리에 그어져 있다는 게 조금 의외였습니다.
함께 읽기
- 스토어 메타데이터 API 정리: 항목별로 되는 것과 안 되는 것앱 설명과 키워드를 채우면서 두 스토어의 API 를 항목별로 다 두드려 봤습니다. 콘솔에 들어가지 않고 어디까지 갈 수 있는지 궁금했거든요.
- 바이너리 등록 후 남은 것들: 자동화되는 일과 사람이 해야 하는 일빌드가 끝나고 양쪽 스토어 콘솔에 바이너리가 올라갔습니다. 저는 그때 "이제 다 됐다"고 생각했습니다. 실제로는 거기서부터가 절반이었습니다.
- Expo 앱 스토어 배포 준비 체크리스트: 순서가 중요한 이유새 Expo 앱 하나를 두 스토어에 올릴 준비를 했습니다. 필요한 항목 자체는 검색하면 다 나옵니다. 그런데 정작 저를 막은 건 항목이 아니라 순서였습니다. 앞 단계가 끝나야 뒤 단계의 값이 생기는 구간이 몇 군데 있고, 그걸 모르고 덤비면 "왜 이 필드가 비어 있지"에서 멈춥니다.
- 스토어 이미지 규격 정리: 스크린샷·아이콘·그래픽에 실제로 필요한 것앱 하나를 App Store와 Google Play에 동시에 올리면서 이미지를 몇 개나 만들었는지 세어봤습니다. 스크린샷 12장, 스토어 전용 그래픽 2개, 앱 번들에 들어가는 아이콘 자산 7개. 합쳐서 21개입니다. 코드를 다 짜놓고 시작한 마무리 작업인데 결국 여기에 시간을 더 썼습니다.
- Google Play 출시 트랙 정리: 내부·비공개·공개·프로덕션과 App Store 비교오늘 성경 지도라는 앱을 App Store와 Google Play 양쪽에 올렸습니다. iOS를 먼저 끝내고 Play Console로 넘어왔는데, 출시 개요 페이지에서 카드 네 장을 보고 잠깐 멈췄습니다.