Service Token Acquisition (oauth)¶
Folder: service_get_token_oauth/
The reference for token_factory's target_tool="oauth" flow, covering both credential shapes it accepts:
- CLI example (
examples/api_key_to_iam_token.py+src/agent_get_token/) — a non-interactive service identity (OCI API key signer credential) exchanged for an oauth token. No browser, no user. - NL2SQL chat app (
src/backend/app.py+ui.html) — an interactive user's IDCS login token exchanged for a fresh oauth token on every request, used to callmcp_sqlcl.
Its counterpart is E2E Identity Propagation → ADB, the oracle-database reference.
1. CLI example — OCI signer credential¶
src/agent_get_token/client.py exposes two helpers that delegate the actual exchange to token_factory.TokenFactory:
from agent_get_token import build_signer_credential, build_signer_only_factory
credential = build_signer_credential(
tenancy_ocid=OCI_IDENTITY_TENANCY_OCID,
user_ocid=OCI_IDENTITY_USER_OCID,
fingerprint=OCI_IDENTITY_FINGERPRINT,
region=OCI_IDENTITY_REGION,
private_key_file="~/.oci/oci_api_key.pem",
)
factory = build_signer_only_factory(
domain_url="https://<domain>.identity.oraclecloud.com",
domain_region=OCI_IDENTITY_REGION,
)
result = factory.get_token(credential, "oauth", scope="urn:opc:idm:__myscopes__")
token = result["token"] # Bearer token string
header = f"{result['token_type']} {token}" # "Bearer eyJ..."
build_signer_only_factory() constructs a TokenFactoryConfig directly instead of going through TokenFactory()'s environment loader, because that loader unconditionally requires JWT-bearer-assertion settings (TOKEN_FACTORY_ISSUER, TOKEN_FACTORY_CREDS, TOKEN_FACTORY_PRIVATE_KEY_*, TOKEN_FACTORY_CERT_*) that a pure OCI signer credential exchange never uses.
Required .env keys:
OCI_IDENTITY_TOKEN_URL=https://<domain>.identity.oraclecloud.com/oauth2/v1/token
OCI_IDENTITY_TENANCY_OCID=ocid1.tenancy.oc1..
OCI_IDENTITY_USER_OCID=ocid1.user.oc1..
OCI_IDENTITY_FINGERPRINT=xx:xx:xx:..
OCI_IDENTITY_REGION=us-chicago-1
OCI_IDENTITY_PRIVATE_KEY_FILE=~/.oci/oci_api_key.pem
When to use: CLI agents, background jobs, or any non-interactive service that is an OCI principal and needs a short-lived Bearer token without storing a client secret.
2. NL2SQL chat app — user access token¶
A user signs in via IDCS OAuth2 (authorization-code flow), asks a question in plain English, and the backend translates it to SQL and runs it via mcp_sqlcl. Every call authenticates to mcp_sqlcl with a freshly oauth-exchanged token, not the user's raw login token — a direct, literal instance of token_factory's documented oauth example, inline, per request:
from auth.token_factory import TokenFactory
factory = TokenFactory()
result = factory.get_token(caller_access_token, "oauth", scope=oauth_scope)
oauth_access_token = result["token"]
The exchange happens fresh on every call rather than once at login, since the exchanged token's lifetime (a few minutes) is much shorter than the original IDCS login token's — nothing about it is worth caching client-side.
Architecture¶
Browser ──► FastAPI backend (port 8000)
│ OAuth2 authorization code flow (IDCS), CSRF-protected
│ Stores the caller's original IDCS login token client-side
▼
/nl2sql: TokenFactory.get_token(caller_token, "oauth", scope=...)
│ fresh downstream oauth token, per request
▼
MCP SQLcl server (port 3001)
│ OCITokenAuthMiddleware validates the exchanged JWT (JWKS)
│ schema_information → OCI GenAI (NL2SQL) → execute_sql
▼
OCI Autonomous Database
Prerequisites¶
- IDCS confidential application with
authorization_codeandJWT Assertiongrants enabled, and theTOKEN_FACTORY_KIDcertificate loaded into the Identity Domain certificate store mcp_sqlclrunning on port 3001- OCI GenAI model access (compartment + model id)
When to use: Any interactive application that needs to call a Bearer-token-secured MCP server or REST API on behalf of a logged-in user, where the downstream system doesn't need OCI-SDK-signer-level access.