API authentication
Send an access token or service token to D.Hub APIs in the Authorization: Bearer header. External apps that act as a signed-in user handle sign-in, token storage, and API calls through a BFF (Backend for Frontend).
| Integration | Sign-in and refresh | API caller |
|---|---|---|
| D.Hub Portal | Internal OIDC client and Manager's HttpOnly refresh cookie | Portal, using an access token |
| External app acting as a user | Confidential OIDC client, PKCE, and server-side token storage | BFF, after exchanging a token for the target service |
| Automation without a user | Service-account token or M2M client | Automation server |
For external apps, start with User-identity access for external apps below. Do not send a user's password to /api/v1/auth/login on their behalf or share Portal cookies for this integration.
User-identity access for external apps
Use this feature only where the supporting Manager and Portal versions are deployed. Grant exchange permissions only to apps trusted by your organization. Per-app API scope ceilings are not yet supported: registering an audience does not reduce the user's existing permissions to read-only access. Before opening access to external vendors, implement scope limits and verify calls between the target APIs and other services.
Register the client
An administrator registers the app in OIDC Clients with these settings.
| Field | Setting |
|---|---|
| Client type | Confidential |
| Grant type | Select Authorization code only |
| Redirect URI | The BFF's HTTPS callback, such as https://partner.example/oidc/callback |
| Require PKCE | Enabled |
| Allow refresh token | Enabled |
| Token exchange audiences | Select only services the app calls, such as dhub2-manager |
Store the issued client secret in the BFF's secret store. The service-token Allowed audience field does not configure permissions for user sign-in apps. New Public clients support SSO only; refresh and token exchange are unavailable.
Sign in and call APIs
- The BFF obtains authorization and token endpoints from Manager's
/.well-known/openid-configuration. Use an OIDC library to create PKCE S256,state, andnonce, and keep the sign-in state and verifier in the server session. - Redirect the browser to the authorization endpoint. Send
scope=openid profile email, the registered Client ID and Redirect URI,state,nonce, and the PKCE challenge. Do not request API-specific scopes at this stage. - At the BFF callback, verify
stateand request tokens with the code, redirect URI, and verifier. Authenticate the client withclient_secret_basicorclient_secret_post. Use the OIDC library to validate the ID token's signature, issuer, audience, and nonce. - Store the returned access token, ID token, and refresh token on the BFF. Manager delivers refresh tokens for external Confidential clients only in the JSON response body. Give the browser only the BFF's own session cookie, and enforce session authentication and CSRF protection in the BFF.
- The sign-in access token's audience is the app's Client ID. Exchange it for an access token addressed to the target service before calling backend APIs.
Send the exchange request from the BFF to the discovered token endpoint as application/x-www-form-urlencoded. Authenticate the client and include these fields.
| Field | Value |
|---|---|
grant_type | urn:ietf:params:oauth:grant-type:token-exchange |
subject_token_type | urn:ietf:params:oauth:token-type:access_token |
subject_token | The access token issued when the user signed in to this app |
audience | One target service, such as dhub2-manager |
The BFF sends the returned access_token to that API as Authorization: Bearer. The sub remains the user, and existing user permissions apply. An ID token, another app's access token, or an already exchanged token cannot be used as the exchange source.
To call another service, exchange the original sign-in token separately for that service. Manager rejects a token addressed to dhub2-agent. Paths that call Manager internally, such as Agent tools, require separate delegation design and verification. Registering an audience alone does not verify the full call chain.
Browser CORS does not apply to server-to-server BFF requests. You do not need to add the external app's domain to Manager's CORS_ORIGINS or REFRESH_COOKIE_ORIGINS. The BFF does not use Manager cookies. Browser requests that use only a Bearer header do not need credentials: "include".
Refresh tokens and sign out through the BFF
Before the original access token expires, the BFF authenticates the client at the token endpoint and sends these fields.
| Field | Value |
|---|---|
grant_type | refresh_token |
refresh_token | The current refresh token stored by the BFF |
Serialize refresh requests for each user session. Replace the stored refresh token with the new value from the response, then exchange the new sign-in access token for target-service tokens. The previous refresh token is revoked; do not reuse it. Partition token caches by user session, client, audience, and scope, and never use expired entries.
On sign-out, delete the BFF session and token cache. Authenticate the client and send the current refresh token with token_type_hint=refresh_token to Manager's /revoke. Revoking the original access token separately can prevent new exchanges. Already issued exchanged tokens may remain valid until expiry. BFF sign-out and IdP SSO sign-out are separate operations.
See the Manager integration guide for deployment-policy migration and policy states.
Authentication flow
The email/password and cookie examples below cover Portal local sign-in and administrator checks of that environment. External apps use the BFF OIDC sign-in and refresh procedure above.
Obtain a token by signing in
Sign in with an email address and password.
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();
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer"
}
The sign-in response does not include refresh_token in JSON. The server sets an HttpOnly cookie. Continue using the cookie file in cURL or the same Session in Python. Browser JavaScript sends the cookie with credentials: "include" without reading its value.
Call an API
Include the issued access_token in the Authorization header.
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,
)
- Load tokens from environment variables or a secret store instead of putting them in source code.
- Check that client code and logs do not expose tokens.
Refresh tokens
For Portal local sign-in, send the refresh cookie to /api/v1/auth/refresh when the access token expires. This endpoint does not accept refresh_token in the request body. The request's Origin must be a Portal origin allowed by the administrator. Replace {portal_origin} in the cURL and Python checks below with the registered Portal origin. Browsers send this header automatically.
Portal OIDC sign-in sends grant_type=refresh_token and the Portal client_id to the discovered token endpoint, using the refresh cookie. Keep this separate from the external BFF's body-based 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();
Service tokens
Use service tokens for automation scripts, CI/CD, and external-system integrations. An administrator creates a service account and issues its token through POST /api/v1/admin/tokens. Regular user accounts cannot receive service 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
}'
The response's token value is shown only at issuance. Keep it in a secret store and send it in Authorization: Bearer, as with an access token.
curl -X GET https://{host}/api/v1/datasets \
-H "Authorization: Bearer {service_token}"
See External data access to query loaded data with a service account and write it to an external database.
For issue, list, and revoke request/response contracts, see the admin token operations in the generated API reference.
Knowledge Chat API authentication
RAG-based Knowledge Chat provides an OpenAI Chat Completions-compatible endpoint at {knowledge_base}/v1/chat/completions. It uses a separate service address from the core API (/api/v1). Supply an access token addressed to that service as api_key. An external BFF uses a token exchanged with 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": "서울시 교통 현황을 알려줘"}
],
)
Common authentication errors
| Status | Cause | Resolution |
|---|---|---|
401 Unauthorized | Missing or expired token | Sign in for a new token or refresh it |
401 Unauthorized | Token audience differs from the target service | Exchange a token for the correct service |
401 Unauthorized | Invalid token format | Check the Bearer prefix |
403 Forbidden | No permission for the requested asset | Ask an administrator for access |
Next steps
- See API client tools for examples using different clients.
- See the API reference for endpoint requests and responses.