본문으로 건너뛰기

API 인증

D.Hub API에는 Authorization: Bearer 헤더로 access token이나 서비스 토큰을 전달합니다. 사용자 신원으로 호출하는 외부 앱은 **BFF(앱 전용 백엔드)**에서 로그인·토큰 보관·API 호출을 처리합니다.

연동 대상로그인·갱신 방식API 호출 주체
D.Hub Portal내부 OIDC 클라이언트와 Manager의 HttpOnly refresh 쿠키Portal이 access token으로 호출
사용자 신원을 쓰는 외부 앱Confidential OIDC 클라이언트, PKCE, BFF에 토큰 보관BFF가 대상 서비스용 토큰을 교환해 호출
사용자 없이 실행하는 자동화서비스 계정의 서비스 토큰 또는 M2M 클라이언트자동화 서버

외부 앱 개발은 아래 외부 앱의 사용자 신원 연동 절부터 진행합니다. /api/v1/auth/login에 사용자의 비밀번호를 대신 보내거나 Portal 쿠키를 공유하는 방식은 외부 앱 연동에 사용하지 않습니다.

외부 앱의 사용자 신원 연동​

현재 지원 범위

이 기능은 해당 Manager와 Portal 버전이 배포된 환경에서 사용합니다. 현재는 조직이 신뢰하는 앱에만 교환 권한을 부여합니다. 앱별 API scope 상한은 아직 지원하지 않으므로 audience를 등록해도 사용자의 기존 권한을 읽기 전용으로 줄이지 않습니다. 외부 업체에 개방하기 전에는 scope 제한과 대상 API의 서비스 간 호출 검증이 필요합니다.

클라이언트 등록​

관리자는 OIDC 클라이언트에서 다음과 같이 등록합니다.

항목설정
Client 유형Confidential
Grant 타입인증 코드 하나만 선택
Redirect URIBFF의 HTTPS callback 주소, 예: https://partner.example/oidc/callback
PKCE 필수켬
Refresh 토큰 허용켬
교환 허용 Audience실제 호출할 서비스만 선택, 예: dhub2-manager

발급한 client secret은 BFF의 비밀 저장소에 보관합니다. 서비스 토큰용 허용 Audience는 사용자 로그인 앱의 권한 설정에 사용하지 않습니다. Public 클라이언트는 신규 등록 시 SSO 전용이며 refresh와 token exchange를 허용하지 않습니다.

로그인과 API 호출​

  1. BFF가 Manager의 /.well-known/openid-configuration에서 인증·토큰 엔드포인트를 조회합니다. OIDC 라이브러리로 PKCE S256, state, nonce를 만들고 로그인 상태와 verifier를 서버 세션에 저장합니다.
  2. 브라우저를 인증 엔드포인트로 보냅니다. scope=openid profile email과 등록한 Client ID·Redirect URI, state, nonce, PKCE challenge를 전달합니다. 현재 API 전용 scope는 요청하지 않습니다.
  3. BFF callback에서 state를 확인하고 code·redirect URI·verifier로 토큰을 요청합니다. 클라이언트 인증은 client_secret_basic 또는 client_secret_post를 사용합니다. ID token의 서명·issuer·audience·nonce는 OIDC 라이브러리로 검증합니다.
  4. BFF가 응답의 access token·ID token·refresh token을 서버에 보관합니다. Manager는 외부 Confidential 클라이언트에 refresh token을 JSON 본문으로만 전달합니다. 브라우저에는 BFF 자신의 세션 쿠키만 전달하고 BFF에서 세션 인증과 CSRF 방어를 적용합니다.
  5. 로그인 access token의 audience는 앱의 Client ID입니다. 이 토큰으로 백엔드 API를 직접 호출하지 않고 대상 서비스용 access token으로 교환합니다.

교환은 BFF에서 discovery의 token endpoint로 보내는 application/x-www-form-urlencoded 요청입니다. 클라이언트 인증과 함께 다음 필드를 전달합니다.

필드값
grant_typeurn:ietf:params:oauth:grant-type:token-exchange
subject_token_typeurn:ietf:params:oauth:token-type:access_token
subject_token이 앱이 로그인으로 받은 access token
audience호출할 서비스 하나, 예: dhub2-manager

BFF는 응답의 access_token을 Authorization: Bearer로 대상 API에 전달합니다. sub는 사용자이며 기존 사용자 권한으로 처리합니다. ID token이나 다른 앱의 access token, 이미 교환한 토큰은 교환 원본으로 사용할 수 없습니다.

다른 서비스를 호출할 때는 원본 로그인 토큰에서 해당 서비스용 토큰을 따로 교환합니다. dhub2-agent용 토큰을 Manager에 보내면 거절됩니다. Agent 도구처럼 내부에서 Manager를 다시 호출하는 경로는 별도 위임 설계와 검증이 필요합니다. 대상 audience를 등록했다는 이유만으로 전체 호출 경로가 동작한다고 가정하지 않습니다.

CORS와 쿠키

BFF의 서버 간 요청에는 브라우저 CORS가 적용되지 않습니다. 외부 앱의 도메인을 Manager의 CORS_ORIGINS나 REFRESH_COOKIE_ORIGINS에 추가할 필요가 없습니다. BFF는 Manager 쿠키를 사용하지 않습니다. 브라우저가 Bearer 헤더만 사용하는 요청에는 credentials: "include"가 필요하지 않습니다.

BFF 토큰 갱신과 로그아웃​

원본 access token 만료 전에 BFF가 token endpoint에 클라이언트 인증과 다음 필드를 보냅니다.

필드값
grant_typerefresh_token
refresh_tokenBFF에 저장한 현재 refresh token

갱신 요청은 사용자 세션별로 직렬화합니다. 응답의 새 refresh token으로 기존 값을 교체하고 새 로그인 access token에서 대상 서비스용 토큰을 다시 교환합니다. 이전 refresh token은 철회되므로 재사용하지 않습니다. 토큰 캐시는 사용자 세션·클라이언트·audience·scope별로 구분하고 만료 후에는 사용하지 않습니다.

로그아웃 시 BFF 세션과 토큰 캐시를 삭제합니다. 클라이언트 인증을 포함해 Manager의 /revoke에 현재 refresh token과 token_type_hint=refresh_token을 전달합니다. 원본 access token도 개별 철회하면 새 교환을 막을 수 있습니다. 이미 발급한 교환 토큰은 만료까지 유효할 수 있으며 BFF 로그아웃과 IdP의 SSO 로그아웃은 별개입니다.

서버 설정 이관과 정책 상태는 Manager 연동 가이드에서 확인합니다.

인증 흐름​

이하 이메일·비밀번호와 쿠키 예시는 Portal의 로컬 로그인 및 해당 환경을 점검하는 관리자용입니다. 외부 앱의 OIDC 로그인·갱신은 위 BFF 절차를 따릅니다.

로그인으로 토큰 발급하기​

로그인은 이메일과 비밀번호로 수행합니다.

cURL​

curl -c cookies.txt -X POST https://{host}/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"password": "your-password"
}'

Python​

import requests

session = requests.Session()
response = session.post(
"https://{host}/api/v1/auth/login",
json={
"email": "you@example.com",
"password": "your-password",
},
)

data = response.json()
access_token = data["access_token"]

JavaScript​

const response = await fetch("https://{host}/api/v1/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({
email: "you@example.com",
password: "your-password",
}),
});

const { access_token } = await response.json();

응답​

{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer"
}

로그인 응답은 refresh_token을 JSON 본문에 포함하지 않습니다. 서버가 HttpOnly 쿠키로 설정하므로 cURL은 쿠키 파일을, Python은 같은 Session 객체를 이어서 사용합니다. 브라우저 JavaScript에서는 쿠키 값을 직접 읽지 않고 credentials: "include"로 전송합니다.

API 호출​

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

curl -X GET https://{host}/api/v1/datasets \
-H "Authorization: Bearer {access_token}"
headers = {"Authorization": f"Bearer {access_token}"}

response = requests.get(
"https://{host}/api/v1/datasets",
headers=headers,
)
토큰 보안
  • 토큰은 소스 코드에 직접 입력하지 않고 환경 변수나 비밀 값 저장소에서 불러옵니다.
  • 클라이언트 코드와 로그에 토큰이 노출되지 않도록 확인합니다.

토큰 갱신​

Portal의 로컬 로그인에서는 access_token 만료 시 /api/v1/auth/refresh로 refresh 쿠키를 보냅니다. 이 경로는 요청 본문에 refresh_token을 넣지 않습니다. 요청의 Origin은 관리자에게 허용된 Portal 출처여야 합니다. 아래 cURL·Python 점검 예시의 {portal_origin}에는 실제 등록된 Portal의 출처를 입력합니다. 브라우저는 이 헤더를 자동으로 보냅니다.

Portal의 OIDC 로그인은 discovery의 token endpoint에 grant_type=refresh_token과 Portal의 client_id를 보내며 refresh 쿠키를 사용합니다. 외부 BFF의 본문 refresh와 혼용하지 않습니다.

curl -b cookies.txt -X POST https://{host}/api/v1/auth/refresh \
-H "Origin: {portal_origin}"
response = session.post(
"https://{host}/api/v1/auth/refresh",
headers={"Origin": "{portal_origin}"},
)

data = response.json()
access_token = data["access_token"]
const response = await fetch("https://{host}/api/v1/auth/refresh", {
method: "POST",
credentials: "include",
});

const { access_token } = await response.json();

서비스 토큰​

자동화 스크립트, CI/CD, 외부 시스템 연동에는 서비스 토큰을 사용합니다. 관리자가 서비스 계정을 만든 뒤 POST /api/v1/admin/tokens로 해당 계정의 토큰을 발급합니다. 일반 사용자 계정에는 서비스 토큰을 발급할 수 없습니다.

# 관리자가 서비스 계정의 토큰 발급
curl -X POST https://{host}/api/v1/admin/tokens \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"user_id": "{service_account_id}",
"name": "data-pipeline-bot",
"description": "자동화 파이프라인 연동용",
"expires_in_days": 180
}'

응답의 token 값은 발급할 때 한 번만 표시됩니다. 안전한 비밀 저장소에 보관하고, 일반 access_token과 동일하게 Authorization: Bearer 헤더로 사용합니다.

curl -X GET https://{host}/api/v1/datasets \
-H "Authorization: Bearer {service_token}"

서비스 계정으로 적재 데이터를 조회하고 외부 데이터베이스에 반영하는 절차는 외부 데이터 조회에서 확인합니다.

팁

발급·조회·폐기 엔드포인트의 요청과 응답은 자동 생성 API 참조의 admin 토큰 항목에서 확인합니다.

Knowledge Chat API 인증​

RAG 기반 Knowledge Chat은 OpenAI Chat Completions와 호환되는 엔드포인트({knowledge_base}/v1/chat/completions)를 제공합니다. 이 엔드포인트는 핵심 API(/api/v1)와 별도의 서비스 주소에서 동작하며, api_key에 해당 서비스를 대상으로 하는 access token을 전달합니다. 외부 BFF는 audience=dhub2-knowledge로 교환한 토큰을 사용합니다.

from openai import OpenAI

client = OpenAI(
base_url="https://{knowledge_base}/v1", # Knowledge(RAG) 서비스 주소
api_key="{access_token}",
)

response = client.chat.completions.create(
model="{knowledge_id}",
messages=[
{"role": "user", "content": "서울시 교통 현황을 알려줘"}
],
)

일반적인 인증 오류​

상태 코드원인해결 방법
401 Unauthorized토큰이 없거나 만료됨로그인하여 새 토큰 발급 또는 토큰 갱신
401 Unauthorized대상 서비스와 token audience가 다름올바른 대상 서비스용 토큰을 교환
401 Unauthorized토큰 형식이 잘못됨Bearer 접두사 포함 여부 확인
403 Forbidden해당 자산에 대한 권한 없음관리자에게 접근 권한 요청

다음 단계​