OAuth 2.0 인증
ONDA API 인증 체계와 토큰 관리 방법을 안내합니다.
ONDA Platform Engineering 관리
OAuth 2.0 인증
ONDA Channel API는 OAuth 2.0 Client Credentials 플로우로 서버-투-서버(S2S) 인증을 제공합니다. 발급받은 Access Token을 모든 API 요청의 Authorization 헤더에 포함해야 합니다.
OAuth 2.0 개요
OAuth 2.0은 산업 표준 인증 프로토콜로, 사용자 비밀번호를 직접 공유하지 않고도 안전하게 API 접근 권한을 위임할 수 있습니다.
ONDA Channel API는 Client Credentials 플로우만 지원합니다. Authorization Code, PKCE, Refresh Token 등 사용자 인증 기반 플로우는 지원하지 않습니다.
| 플로우 | 사용 케이스 | 지원 여부 |
|---|---|---|
| Client Credentials | 서버-투-서버 통신 (백엔드) | ✅ 지원 |
Client Credentials 플로우
서버-투-서버 통신에 사용하는 플로우입니다. 대부분의 채널 파트너가 사용합니다.
1. Access Token 발급
Client Credentials 플로우
요청 파라미터:
| 파라미터 | 필수 | 설명 |
|---|---|---|
grant_type | ✅ | client_credentials 고정 |
client_id | ✅ | 앱 생성 시 발급받은 Client ID |
client_secret | ✅ | 앱 생성 시 발급받은 Client Secret |
scope | 요청 권한 범위 (공백 구분, 앱에 부여된 scope의 부분집합). 생략 시 앱의 전체 scope 발급 |
응답:
Access Token은 JWT가 아닌 불투명(opaque) 토큰으로, oat_ 접두사를 가집니다.
{
"access_token": "oat_3f9a2c5e8b1d4f7a9c0e2b6d8f1a3c5e7b9d0f2a4c6e8b0d1f3a5c7e9b1d3f5a",
"token_type": "bearer",
"expires_in": 1800,
"scope": "properties:read reservations:write"
}
2. API 호출
발급받은 Access Token을 Authorization 헤더에 포함하여 API를 호출합니다.
API 호출 예시
권한 범위 (Scope)
API는 deny-by-default 정책을 따릅니다. 토큰에 포함된 scope가 엔드포인트가 요구하는 scope를 포함하지 않으면 403 SCOPE_DENIED를 반환합니다.
| Scope | 권한 |
|---|---|
properties:read | 숙소 목록/상세, 요금제, 콘텐츠 파일 조회 |
roomtypes:read | 객실 타입 조회 |
search:availability | 빈방·가격 검색, price-check |
reservations:read | 예약 조회, 취소 정책/수수료 조회 |
reservations:write | 예약 생성·수정·취소 |
voucher:read | 바우처 조회 |
channels:read | 채널 정보 조회 |
channels:write | 채널 정보(정산 계좌) 수정 |
tax-invoices:read | 세금계산서 조회 |
토큰 라이프사이클
Access Token
- 유효 기간: 30분 (1800초)
- 형태: 불투명 토큰 (
oat_<hex>) - 용도: API 호출 인증
- 저장 위치: 메모리 (권장) 또는 안전한 스토리지
만료된 토큰은 POST /channel/v1/oauth/token을 다시 호출하여 새 토큰을 발급받습니다. Refresh Token은 발급되지 않으므로, 만료 시 Client Credentials 플로우로 재발급합니다.
자동 재발급 구현 예시
import time
import requests
class TokenManager:
def __init__(self, client_id, client_secret,
base_url="https://api.tport.dev/channel/v1"):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url
self.access_token = None
self.expires_at = 0
def get_token(self):
"""현재 유효한 토큰 반환 (필요시 자동 재발급)"""
if time.time() >= self.expires_at - 60: # 만료 1분 전에 재발급
self._issue_token()
return self.access_token
def _issue_token(self):
"""Client Credentials 플로우로 토큰 발급"""
response = requests.post(
f"{self.base_url}/oauth/token",
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
},
)
data = response.json()
self.access_token = data["access_token"]
self.expires_at = time.time() + data["expires_in"]
보안 가이드라인
Client Secret 보호
절대 금지: Client Secret을 클라이언트 측 코드(JavaScript, 모바일 앱 등)에 포함하지 마세요.
✅ 올바른 방법:
- 서버 환경 변수에 저장
- AWS Secrets Manager, HashiCorp Vault 등 사용
.env파일 사용 시.gitignore에 추가
❌ 잘못된 방법:
- Git 저장소에 커밋
- 브라우저 JavaScript에 노출
- 모바일 앱 코드에 하드코딩
Token 저장
| 토큰 유형 | 서버 | 브라우저 | 모바일 앱 |
|---|---|---|---|
| Access Token | 메모리 또는 Redis | 메모리 | Keychain/Keystore |
| Client Secret | 환경 변수 | ❌ 저장 금지 | ❌ 저장 금지 |
HTTPS 필수
모든 API 호출은 HTTPS를 통해서만 가능합니다. HTTP 요청은 자동으로 거부됩니다.
IP 화이트리스트
파트너 센터에서 허용할 IP 주소를 등록하여 추가 보안을 적용할 수 있습니다.
에러 코드
토큰 발급 엔드포인트는 OAuth 표준 형식({error, error_description})으로 에러를 반환합니다.
401 Unauthorized
{
"error": "invalid_client",
"error_description": "Invalid client credentials"
}
원인: Client ID/Secret이 유효하지 않거나 앱이 정지됨 해결: 자격 증명을 확인하세요
400 Bad Request (Invalid Scope)
{
"error": "invalid_scope",
"error_description": "Requested scope exceeds granted scopes"
}
원인: 앱에 부여되지 않은 scope를 요청함 해결: 앱에 부여된 scope 범위 내에서 요청하세요
400 Bad Request (Unsupported Grant Type)
{
"error": "unsupported_grant_type",
"error_description": "Only client_credentials is supported"
}
원인: client_credentials 외의 grant_type을 사용함
해결: grant_type=client_credentials로 요청하세요
다음 단계
- 첫 API 호출 - 실제 API 호출 테스트
- 에러 처리 - API 에러 처리 방법
- OpenAPI 명세 - 인증 관련 전체 API 명세