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 접근 권한을 위임할 수 있습니다.

Interactive demo — use the portal console

ONDA Channel API는 Client Credentials 플로우만 지원합니다. Authorization Code, PKCE, Refresh Token 등 사용자 인증 기반 플로우는 지원하지 않습니다.

플로우사용 케이스지원 여부
Client Credentials서버-투-서버 통신 (백엔드)✅ 지원

Client Credentials 플로우

서버-투-서버 통신에 사용하는 플로우입니다. 대부분의 채널 파트너가 사용합니다.

1. Access Token 발급

Client Credentials 플로우

요청 파라미터:

파라미터필수설명
grant_typeclient_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로 요청하세요

다음 단계