앱 인증 API
앱 인증 API는 모바일 앱과 백엔드 서버 간의 안전한 통신을 위한 인증 메커니즘을 제공합니다.
참고
앱 인증에 대한 자세한 개념 및 보안 원칙은 앱 인증 가이드라인을 참조하세요.
챌린지 요청
앱 인증 프로세스를 시작하기 위해 서버에 챌린지를 요청합니다.
- HTTP Method:
POST - Path:
/de/v1/auth/app/challenge - 인증: 필요 없음
Headers
| Header | Type | Description | Required |
|---|---|---|---|
Content-Type | application/json | 요청 본문 형식을 지정합니다. | Yes |
Request Body
{
"deviceId": {
"uuid": "e8e53358-c282-4f69-89c4-bae40f0b7587",
"platform": "iOS",
"version": "1.2.0",
"timestamp": 1714523700000
},
"deviceIdHash": "f1a23bc45d67e8f9a0b1c2d3e4f56a78b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4"
}
| 필드 | 타입 | 설명 | 필수 |
|---|---|---|---|
deviceId | object | 디바이스 정보 객체 | Yes |
deviceId.uuid | string | 장치 고유 식별자 | Yes |
deviceId.platform | string | 플랫폼 정보 (DevicePlatform 참조) | Yes |
deviceId.version | string | 클라이언트 앱 버전 | Yes |
deviceId.timestamp | number | 요청 생성 시간 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | Yes |
deviceIdHash | string | deviceId 객체를 JSON 직렬화 후 SHA256 해싱한 값 (hex 인코딩) | Yes |
Responses
200 OK - 성공
{
"challenge": "randomBase64EncodedChallengeString",
"nonce": "randomNonceString"
}
| 필드 | 타입 | 설명 | 필수 |
|---|---|---|---|
challenge | string | 클라이언트가 암호화해야 할 챌린지 문자열 | Yes |
nonce | string | 챌린지 완료 시 함께 전송해야 할 값 (재생 공격 방지) | Yes |
400 Bad Request - 잘못된 요청 (예: 유효하지 않은 디바이스 ID)
{
"code": 2011,
"message": "INVALID_DEVICE_ID",
"detail": "디바이스 ID가 유효하지 않거나 필수 필드가 누락되었습니다",
"errors": [
{
"field": "deviceId.uuid",
"message": "uuid는 필수입니다."
}
]
}
400 Bad Request - 잘못된 요청 (예: 디바이스 해시 불일치)
{
"code": 2016,
"message": "DEVICE_HASH_MISMATCH",
"detail": "제공된 deviceIdHash가 서버에서 계산한 값과 일치하지 않습니다"
}
403 Forbidden - 접근 차단 (예: 블랙리스트 디바이스)
{
"code": 2013,
"message": "DEVICE_BLACKLISTED",
"detail": "해당 디바이스는 접근이 차단되었습니다"
}
429 Too Many Requests - 요청 제한 초과
{
"code": 2040,
"message": "RATE_LIMIT_EXCEEDED",
"detail": "요청 횟수가 제한을 초과했습니다"
}
500 Internal Server Error - 서버 내부 오류
{
"code": 2010,
"message": "SERVER_ERROR",
"detail": "서버 내부 오류"
}
설명
- 클라이언트 앱은 시작 시 이 API를 호출하여 인증 프로세스를 시작합니다.
deviceId정보는 앱 및 운영체제에서 제공하는 값을 사용합니다.deviceIdHash는deviceId객체를 안정적인 순서로 JSON 직렬화한 후 SHA256 해시 함수를 적용하여 생성해야 합니다. (예: 알파벳 순서로 키 정렬 후 직렬화)
챌린지 완료
챌린지 요청으로 받은 challenge 값을 암호화하여 서버에 전송하고, 성공 시 앱 인증 토큰(appToken)을 발급받습니다.
- HTTP Method:
POST - Path:
/de/v1/auth/app/complete-challenge - 인증: 필요 없음
Headers
| Header | Type | Description | Required |
|---|---|---|---|
Content-Type | application/json | 요청 본문 형식을 지정합니다. | Yes |
Request Body
{
"deviceIdHash": "f1a23bc45d67e8f9a0b1c2d3e4f56a78b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4",
"encryptedChallenge": "base64EncodedEncryptedChallenge"
}
| 필드 | 타입 | 설명 | 필수 |
|---|---|---|---|
deviceIdHash | string | 챌린지 요청 시 사용했던 deviceId 객체의 SHA256 해시 값 | Yes |
encryptedChallenge | string | 챌린지 요청 응답의 challenge 값을 클라이언트에서 시간 기반 키로 AES-GCM 암호화하고 Base64 인코딩한 값 | Yes |
Responses
200 OK - 성공
{
"appToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"appSecret": "secureRandomAppSecretString",
"expiresAt": 1714527300000,
"device": {
"id": "device_123",
"uuid": "e8e53358-c282-4f69-89c4-bae40f0b7587",
"platform": "iOS",
"version": "1.2.0",
"createdAt": 1714523700000,
"lastActiveAt": 1714523700000
}
}
| 필드 | 타입 | 설명 | 필수 |
|---|---|---|---|
appToken | string | 앱 인증 토큰 (JWT 형식) | Yes |
appSecret | string | 앱에서 내부적으로 사용할 수 있는 시크릿 값 (클라이언트는 안전하게 저장해야 함) | Yes |
expiresAt | number | appToken의 만료 시간 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | Yes |
device | object | 인증된 디바이스 정보 객체 | Yes |
device.id | string | 서버에서 관리하는 디바이스 고유 ID | Yes |
device.uuid | string | 클라이언트가 제공한 UUID | Yes |
device.platform | string | 클라이언트 플랫폼 (DevicePlatform 참조) | Yes |
device.version | string | 클라이언트 앱 버전 | Yes |
device.createdAt | number | 디바이스 최초 등록 시간 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | Yes |
device.lastActiveAt | number | 디바이스 마지막 활동 시간 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64) | Yes |
400 Bad Request - 잘못된 요청 (예: 유효하지 않은 Nonce)
{
"code": 2050,
"message": "INVALID_NONCE",
"detail": "제공된 nonce가 유효하지 않거나 이미 사용되었습니다"
}
401 Unauthorized - 인증 실패 (예: 챌린지 검증 실패)
{
"code": 2012,
"message": "CHALLENGE_VERIFICATION_FAILED",
"detail": "챌린지 검증(복호화 또는 값 비교)에 실패했습니다"
}
403 Forbidden - 접근 차단 (예: 블랙리스트 디바이스)
{
"code": 2013,
"message": "DEVICE_BLACKLISTED",
"detail": "해당 디바이스는 접근이 차단되었습니다"
}
429 Too Many Requests - 요청 제한 초과
{
"code": 2040,
"message": "RATE_LIMIT_EXCEEDED",
"detail": "요청 횟수가 제한을 초과했습니다"
}
500 Internal Server Error - 서버 내부 오류
{
"code": 2010,
"message": "SERVER_ERROR",
"detail": "서버 내부 오류"
}
설명
- 클라이언트는 챌린지 요청에서 받은
challenge값을 특정 알고리즘(예: 서버 시간과 동기화된 키를 사용하는 AES-GCM)으로 암호화하여encryptedChallenge필드에 담아 전송해야 합니다. hashedDeviceId는 이전 단계에서 사용한 값과 동일해야 합니다.nonce는 챌린지 요청에서 받은 값을 그대로 사용하며, 한 번만 유효합니다.- 성공 시 발급되는
appToken은 이후 앱 전용 API 호출 시Authorization: Bearer {appToken}헤더에 포함되어야 합니다.
오류 참조
자세한 오류 코드와 메시지에 대한 설명은 Auth 도메인 오류 코드 참조를 참조하세요.
공통 오류 응답 형식
모든 오류 응답은 다음과 같은 공통 형식을 따릅니다. 특정 오류에 대한 errors나 metadata 필드는 선택적으로 포함될 수 있습니다.
{
"code": 2000, // 애플리케이션 정의 오류 코드
"message": "ERROR_CODE_NAME", // 오류 코드에 대한 문자열 표현
"detail": "사용자 친화적 오류 메시지", // 상세 설명 (선택 사항)
"metadata": {
"exampleKey": "exampleValue"
}
}
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
code | number | Y | 애플리케이션 오류 코드 |
message | string | Y | 오류 메시지 |
detail | string | N | 상세 설명 |
metadata | object | N | 오류 관련 추가 메타데이터 (선택 사항) |
인증 토큰 사용
앱 인증 토큰(appToken)을 획득한 후, 사용자 인증 전 API 또는 앱 자체 인증만 필요한 API 요청의 헤더에 포함시켜 사용합니다:
Authorization: Bearer {appToken}
API에 따라 사용자 인증이 필요한 경우, 별도의 사용자 인증(로그인)을 진행하고 발급받은 accessToken을 사용해야 합니다.