본문으로 건너뛰기
버전: 0.67.0

앱 인증 API

앱 인증 API는 모바일 앱과 백엔드 서버 간의 안전한 통신을 위한 인증 메커니즘을 제공합니다.

공통 요청 헤더

모든 dha-sleep API 요청은 공통 요청 헤더를 준수해야 합니다. User-Agent, Accept-Language, 인증 헤더 요구사항을 먼저 확인하세요.

참고

앱 인증에 대한 자세한 개념 및 보안 원칙은 앱 인증 가이드라인을 참조하세요.

챌린지 요청​

앱 인증 프로세스를 시작하기 위해 서버에 챌린지를 요청합니다.

  • HTTP Method: POST
  • Path: /v1/auth/app/challenge (API_PREFIX 기본값이 v1이며 환경에 따라 변경 가능)
  • 인증: 필요 없음

Headers​

HeaderTypeDescriptionRequired
Content-Typeapplication/json요청 본문 형식을 지정합니다.Yes

Request Body​

{
"deviceId": {
"uuid": "e8e53358-c282-4f69-89c4-bae40f0b7587",
"platform": "iOS",
"version": "1.2.0",
"timestamp": 1714523700000
},
"deviceIdHash": "f1a23bc45d67e8f9a0b1c2d3e4f56a78b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4"
}
필드타입설명필수
deviceIdobject디바이스 정보 객체Yes
deviceId.uuidstring장치 고유 식별자Yes
deviceId.platformstring플랫폼 정보 (예: iOS, Android)Yes
deviceId.versionstring클라이언트 앱 버전Yes
deviceId.timestampnumber요청 생성 시간 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)Yes
deviceIdHashstringdeviceId 객체를 JSON 직렬화 후 SHA256 해싱한 값 (hex 인코딩)Yes

Responses​

200 OK - 성공
{
"challenge": "randomBase64EncodedChallengeString",
"nonce": "randomNonceString"
}
필드타입설명필수
challengestring클라이언트가 암호화해야 할 챌린지 문자열Yes
noncestring챌린지 완료 시 함께 전송해야 할 값 (재생 공격 방지)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​

HeaderTypeDescriptionRequired
Content-Typeapplication/json요청 본문 형식을 지정합니다.Yes

Request Body​

{
"deviceIdHash": "f1a23bc45d67e8f9a0b1c2d3e4f56a78b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4",
"encryptedChallenge": "base64EncodedEncryptedChallenge"
}
필드타입설명필수
deviceIdHashstring챌린지 요청 시 사용했던 deviceId 객체의 SHA256 해시 값Yes
encryptedChallengestringnonce + 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
}
}
필드타입설명필수
appTokenstring앱 인증 토큰 (JWT 형식)Yes
appSecretstring앱에서 내부적으로 사용할 수 있는 시크릿 값 (클라이언트는 안전하게 저장해야 함)Yes
expiresAtnumberappToken의 만료 시간 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)Yes
deviceobject인증된 디바이스 정보 객체Yes
device.idstring서버에서 관리하는 디바이스 고유 IDYes
device.uuidstring클라이언트가 제공한 UUIDYes
device.platformstring클라이언트 플랫폼 (예: iOS, Android)Yes
device.versionstring클라이언트 앱 버전Yes
device.createdAtnumber디바이스 최초 등록 시간 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)Yes
device.lastActiveAtnumber디바이스 마지막 활동 시간 (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"
}
}
필드타입필수설명
codenumberY애플리케이션 오류 코드
messagestringY오류 메시지
detailstringN상세 설명
metadataobjectN오류 관련 추가 메타데이터 (선택 사항)

인증 토큰 사용​

앱 인증 토큰(appToken)을 획득한 후, 사용자 인증 전 API 또는 앱 자체 인증만 필요한 API 요청의 헤더에 포함시켜 사용합니다:

Authorization: Bearer {appToken}

API에 따라 사용자 인증이 필요한 경우, 별도의 사용자 인증(로그인)을 진행하고 발급받은 accessToken을 사용해야 합니다.