본문으로 건너뛰기
버전: 개발 버전 (최신)

앱 인증 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 정보는 앱 및 운영체제에서 제공하는 값을 사용합니다.
  • deviceIdHashdeviceId 객체를 안정적인 순서로 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": "서버 내부 오류"
}

설명

  • 클라이언트는 noncechallenge 값을 순서대로 이어붙인 문자열을 서버 RSA 공개키(OAEP-SHA256)로 암호화해 Base64로 인코딩한 값을 encryptedChallenge로 전송해야 합니다.
  • deviceIdHash는 챌린지 요청에서 사용한 값과 동일해야 합니다.
  • nonce는 챌린지 요청에서 받은 값을 그대로 사용하며, 한 번만 유효합니다.
  • 성공 시 발급되는 appToken은 이후 앱 전용 API 호출 시 Authorization: Bearer {appToken} 헤더에 포함되어야 합니다.

오류 참조

오류 코드와 메시지는 Swagger 스키마의 응답 예시를 우선 참고하세요.

공통 오류 응답 형식

모든 오류 응답은 다음과 같은 공통 형식을 따릅니다. 특정 오류에 대한 errorsmetadata 필드는 선택적으로 포함될 수 있습니다.

{
"code": 2000, // 애플리케이션 정의 오류 코드
"message": "ERROR_CODE_NAME", // 오류 코드에 대한 문자열 표현
"detail": "사용자 친화적 오류 메시지", // 상세 설명 (선택 사항)
"metadata": {
"exampleKey": "exampleValue"
}
}
필드타입필수설명
codenumberY애플리케이션 오류 코드
messagestringY오류 메시지
detailstringN상세 설명
metadataobjectN오류 관련 추가 메타데이터 (선택 사항)

인증 토큰 사용

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

Authorization: Bearer {appToken}

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