앱 인증 API
앱 인증 API는 모바일 앱과 백엔드 서버 간의 안전한 통신을 위한 인증 메커니즘을 제공합니다.
공통 요청 헤더
모든 dha-sleep API 요청은 공통 요청 헤더를 준수해야 합니다. User-Agent, Accept-Language, 인증 헤더 요구사항을 먼저 확인하세요.
참고
앱 인증에 대한 자세한 개념 및 보안 원칙은 앱 인증 가이드라인을 참조하세요.
챌린지 요청
앱 인증 프로세스를 시작하기 위해 서버에 챌린지를 요청합니다.
- HTTP Method:
POST - Path:
/v1/auth/app/challenge(API_PREFIX기본값이v1이며 환경에 따라 변경 가능) - 인증: 필요 없음
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 | 플랫폼 정보 (예: iOS, Android) | 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:
/v1/auth/app/complete-challenge(API_PREFIX기본값이v1이며 환경에 따라 변경 가능) - 인증: 필요 없음
Headers
| Header | Type | Description | Required |
|---|---|---|---|
Content-Type | application/json | 요청 본문 형식을 지정합니다. | Yes |
Request Body
{
"deviceIdHash": "f1a23bc45d67e8f9a0b1c2d3e4f56a78b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4",
"encryptedChallenge": "base64EncodedEncryptedChallenge"
}
| 필드 | 타입 | 설명 | 필수 |
|---|---|---|---|
deviceIdHash | string | 챌린지 요청 시 사용했던 deviceId 객체의 SHA256 해시 값 | Yes |
encryptedChallenge | string | nonce + challenge 문자열을 서버 RSA 공개키로 OAEP-SHA256 방식으로 암호화한 뒤 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 | 클라이언트 플랫폼 (예: iOS, Android) | 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": "서버 내부 오류"
}
설명
- 클라이언트는
nonce와challenge값을 순서대로 이어붙인 문자열을 서버 RSA 공개키(OAEP-SHA256)로 암호화해 Base64로 인코딩한 값을encryptedChallenge로 전송해야 합니다. deviceIdHash는 챌린지 요청에서 사용한 값과 동일해야 합니다.nonce는 챌린지 요청에서 받은 값을 그대로 사용하며, 한 번만 유효합니다.- 성공 시 발급되는
appToken은 이후 앱 전용 API 호출 시Authorization: Bearer {appToken}헤더에 포함되어야 합니다.
오류 참조
오류 코드와 메시지는 Swagger 스키마의 응답 예시를 우선 참고하세요.
공통 오류 응답 형식
모든 오류 응답은 다음과 같은 공통 형식을 따릅니다. 특정 오류에 대한 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을 사용해야 합니다.