Skip to main content

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).

IntegrationSign-in and refreshAPI caller
D.Hub PortalInternal OIDC client and Manager's HttpOnly refresh cookiePortal, using an access token
External app acting as a userConfidential OIDC client, PKCE, and server-side token storageBFF, after exchanging a token for the target service
Automation without a userService-account token or M2M clientAutomation 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​

Current support

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.

FieldSetting
Client typeConfidential
Grant typeSelect Authorization code only
Redirect URIThe BFF's HTTPS callback, such as https://partner.example/oidc/callback
Require PKCEEnabled
Allow refresh tokenEnabled
Token exchange audiencesSelect 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​

  1. The BFF obtains authorization and token endpoints from Manager's /.well-known/openid-configuration. Use an OIDC library to create PKCE S256, state, and nonce, and keep the sign-in state and verifier in the server session.
  2. 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.
  3. At the BFF callback, verify state and request tokens with the code, redirect URI, and verifier. Authenticate the client with client_secret_basic or client_secret_post. Use the OIDC library to validate the ID token's signature, issuer, audience, and nonce.
  4. 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.
  5. 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.

FieldValue
grant_typeurn:ietf:params:oauth:grant-type:token-exchange
subject_token_typeurn:ietf:params:oauth:token-type:access_token
subject_tokenThe access token issued when the user signed in to this app
audienceOne 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.

CORS and cookies

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.

FieldValue
grant_typerefresh_token
refresh_tokenThe 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,
)
Token security
  • 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.

tip

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​

StatusCauseResolution
401 UnauthorizedMissing or expired tokenSign in for a new token or refresh it
401 UnauthorizedToken audience differs from the target serviceExchange a token for the correct service
401 UnauthorizedInvalid token formatCheck the Bearer prefix
403 ForbiddenNo permission for the requested assetAsk an administrator for access

Next steps​