OAuth 2.0 인증

ONDA API 인증 체계와 토큰 관리 방법을 안내합니다.

OAuth 2.0 인증#

ONDA API는 OAuth 2.0 프로토콜을 사용하여 안전한 인증을 제공합니다. API 호출 시 모든 요청에 Access Token을 포함해야 합니다.

OAuth 2.0 개요#

OAuth 2.0은 산업 표준 인증 프로토콜로, 사용자 비밀번호를 직접 공유하지 않고도 안전하게 API 접근 권한을 위임할 수 있습니다.

서버 간 통신에 적합한 플로우입니다. 사용자 상호작용 없이 서비스 자체의 권한으로 API에 접근합니다.

클라이언트
POST /oauth/token
client_id + client_secret
인증 서버
인증 서버
Access Token 발급
expires_in: 900 (15분)
클라이언트
클라이언트
API 요청
Authorization: Bearer {token}
API 서버
API 서버
API 응답
JSON 데이터
클라이언트

ONDA API는 두 가지 OAuth 플로우를 지원합니다:

플로우사용 케이스보안 수준
Client Credentials서버-투-서버 통신 (백엔드)높음
Authorization Code + PKCE사용자 인증이 필요한 경우 (프론트엔드)매우 높음

Client Credentials 플로우#

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

1. Access Token 발급#

Client Credentials 플로우

cURL:

curl -X POST https://api.onda.me/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "scope=read write"

Python:

import requests

response = requests.post(
    "https://api.onda.me/v1/oauth/token",
    data={
        "grant_type": "client_credentials",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET",
        "scope": "read write",
    },
)

token_data = response.json()
access_token = token_data["access_token"]
refresh_token = token_data["refresh_token"]
expires_in = token_data["expires_in"]  # 900 (15분)

Node.js:

const response = await fetch("https://api.onda.me/v1/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    client_id: "YOUR_CLIENT_ID",
    client_secret: "YOUR_CLIENT_SECRET",
    scope: "read write",
  }),
});

const tokenData = await response.json();
const { access_token, refresh_token, expires_in } = tokenData;

요청 파라미터:

파라미터필수설명
grant_typeclient_credentials 고정
client_id채널 생성 시 발급받은 Client ID
client_secret채널 생성 시 발급받은 Client Secret
scope요청 권한 범위 (기본값: read write)

응답:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjaGFubmVsXzEyMzQ1NiIsImlhdCI6MTcwOTM3MDAwMCwiZXhwIjoxNzA5MzcwOTAwLCJzY29wZSI6InJlYWQgd3JpdGUifQ...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "rt_abc123def456",
  "scope": "read write"
}

2. API 호출#

발급받은 Access Token을 Authorization 헤더에 포함하여 API를 호출합니다.

API 호출 예시

cURL:

curl -X GET "https://api.onda.me/v1/properties" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"

Python:

import requests

headers = {
    "Authorization": f"Bearer {access_token}",
    "Accept": "application/json",
}

response = requests.get(
    "https://api.onda.me/v1/properties",
    headers=headers,
)

Node.js:

const response = await fetch("https://api.onda.me/v1/properties", {
  headers: {
    Authorization: `Bearer ${accessToken}`,
    Accept: "application/json",
  },
});

Authorization Code + PKCE 플로우#

사용자 인증이 필요한 경우 사용하는 플로우입니다. PKCE(Proof Key for Code Exchange)를 사용하여 보안을 강화합니다.

1. Authorization URL 생성#

import secrets
import hashlib
import base64

# Code Verifier 생성 (43-128자의 랜덤 문자열)
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')

# Code Challenge 생성 (SHA256 해시)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode('utf-8')).digest()
).decode('utf-8').rstrip('=')

# Authorization URL
auth_url = (
    "https://api.onda.me/v1/oauth/authorize"
    f"?client_id=YOUR_CLIENT_ID"
    f"&redirect_uri=https://your-app.com/callback"
    f"&response_type=code"
    f"&scope=read write"
    f"&code_challenge={code_challenge}"
    f"&code_challenge_method=S256"
)

# 사용자를 이 URL로 리다이렉트
print(f"Redirect to: {auth_url}")

2. Authorization Code로 토큰 교환#

사용자가 인증을 완료하면 redirect_uri로 Authorization Code가 전달됩니다.

import requests

# redirect_uri로 전달된 code 파라미터
authorization_code = "ac_xyz789"

response = requests.post(
    "https://api.onda.me/v1/oauth/token",
    data={
        "grant_type": "authorization_code",
        "client_id": "YOUR_CLIENT_ID",
        "code": authorization_code,
        "redirect_uri": "https://your-app.com/callback",
        "code_verifier": code_verifier,  # 1단계에서 생성한 값
    },
)

token_data = response.json()
access_token = token_data["access_token"]
refresh_token = token_data["refresh_token"]

토큰 라이프사이클#

Access Token#

  • 유효 기간: 15분 (900초)
  • 용도: API 호출 인증
  • 저장 위치: 메모리 (권장) 또는 안전한 스토리지

Refresh Token#

  • 유효 기간: 30일
  • 용도: Access Token 갱신
  • 저장 위치: 암호화된 데이터베이스 또는 Secure Storage

Refresh Token은 절대 클라이언트 측(브라우저)에 저장하지 마세요. 서버에서만 관리해야 합니다.

토큰 갱신#

Access Token이 만료되기 전에 Refresh Token을 사용하여 갱신합니다.

토큰 갱신

Python:

import requests

response = requests.post(
    "https://api.onda.me/v1/oauth/token",
    data={
        "grant_type": "refresh_token",
        "refresh_token": "YOUR_REFRESH_TOKEN",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET",
    },
)

new_token_data = response.json()
new_access_token = new_token_data["access_token"]
new_refresh_token = new_token_data["refresh_token"]

# 기존 토큰을 새 토큰으로 교체
access_token = new_access_token
refresh_token = new_refresh_token

Node.js:

const response = await fetch("https://api.onda.me/v1/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "refresh_token",
    refresh_token: refreshToken,
    client_id: "YOUR_CLIENT_ID",
    client_secret: "YOUR_CLIENT_SECRET",
  }),
});

const newTokenData = await response.json();
accessToken = newTokenData.access_token;
refreshToken = newTokenData.refresh_token;

자동 갱신 구현 예시#

import time
import requests

class TokenManager:
    def __init__(self, client_id, client_secret):
        self.client_id = client_id
        self.client_secret = client_secret
        self.access_token = None
        self.refresh_token = None
        self.expires_at = 0

    def get_token(self):
        """현재 유효한 토큰 반환 (필요시 자동 갱신)"""
        if time.time() >= self.expires_at - 60:  # 만료 1분 전에 갱신
            self._refresh_token()
        return self.access_token

    def _refresh_token(self):
        """토큰 갱신"""
        response = requests.post(
            "https://api.onda.me/v1/oauth/token",
            data={
                "grant_type": "refresh_token" if self.refresh_token else "client_credentials",
                "refresh_token": self.refresh_token,
                "client_id": self.client_id,
                "client_secret": self.client_secret,
            },
        )
        data = response.json()
        self.access_token = data["access_token"]
        self.refresh_token = data["refresh_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
Refresh Token암호화된 DB❌ 저장 금지Secure Storage
Client Secret환경 변수❌ 저장 금지❌ 저장 금지

HTTPS 필수#

모든 API 호출은 HTTPS를 통해서만 가능합니다. HTTP 요청은 자동으로 거부됩니다.

IP 화이트리스트#

파트너 센터에서 허용할 IP 주소를 등록하여 추가 보안을 적용할 수 있습니다.

에러 코드#

401 Unauthorized#

{
  "error": "invalid_token",
  "error_description": "The access token is invalid or has expired"
}

원인: 토큰이 만료되었거나 유효하지 않음 해결: 토큰을 재발급하거나 갱신

403 Forbidden#

{
  "error": "insufficient_scope",
  "error_description": "The request requires higher privileges than provided by the access token"
}

원인: 토큰의 scope가 부족함 해결: 필요한 scope를 포함하여 토큰을 재발급

400 Bad Request (Invalid Grant)#

{
  "error": "invalid_grant",
  "error_description": "The provided refresh token is invalid, expired, or revoked"
}

원인: Refresh Token이 만료되었거나 무효화됨 해결: Client Credentials 플로우로 새 토큰 발급

다음 단계#