Skip to content

Commit ab683f1

Browse files
authored
OCI Provider with Docs (#2389)
* OCI Provider with Docs * Addressing feedback and adding improvements * Addressing feedback and adding improvements * Fixed Step Typo and Images * Fixed Language Identifier * Fixed Code Check Issues * Fixed Identity Propagation Trust Command * Renamed ociprovider to oci and fixed documentation * Renamed ociprovider to oci * Fixed Config URL * Fixed TokenExchange variable * Fixed Environment Variables * Fixed Environment Variables * Fixed Ruff Check * Fixed Code Rabbit Comments * Fixed Code Rabbit Comments
1 parent bc076cb commit ab683f1

File tree

6 files changed

+566
-0
lines changed

6 files changed

+566
-0
lines changed
179 KB
Loading
37.7 KB
Loading
67.8 KB
Loading
59.7 KB
Loading

docs/integrations/oci.mdx

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
---
2+
title: OCI IAM OAuth 🤝 FastMCP
3+
sidebarTitle: Oracle
4+
description: Secure your FastMCP server with OCI IAM OAuth
5+
icon: shield-check
6+
tag: NEW
7+
---
8+
9+
import { VersionBadge } from "/snippets/version-badge.mdx"
10+
11+
<VersionBadge version="2.13.0" />
12+
13+
This guide shows you how to secure your FastMCP server using **OCI IAM OAuth**. Since OCI IAM doesn't support Dynamic Client Registration, this integration uses the [**OIDC Proxy**](/servers/auth/oidc-proxy) pattern to bridge OCI's traditional OAuth with MCP's authentication requirements.
14+
15+
## Configuration
16+
17+
### Prerequisites
18+
19+
1. An OCI cloud Account with access to create an Integrated Application in an Identity Domain.
20+
2. Your FastMCP server's URL (For dev environments, it is http://localhost:8000. For PROD environments, it could be https://mcp.${DOMAIN}.com)
21+
22+
### Step 1: Make sure client access is enabled for JWK's URL
23+
24+
<Steps>
25+
<Step title="Navigate to OCI IAM Domain Settings">
26+
27+
Login to OCI console (https://cloud.oracle.com for OCI commercial cloud).
28+
From "Identity & Security" menu, open Domains page.
29+
On the Domains list page, select the domain that you are using for MCP Authentication.
30+
Open Settings tab.
31+
Click on "Edit Domain Settings" button.
32+
33+
<Frame>
34+
<img src="/images/oci/ocieditdomainsettingsbutton.png" alt="OCI console showing the Edit Domain Settings button in the IAM Domain settings page" />
35+
</Frame>
36+
</Step>
37+
38+
<Step title="Update Domain Setting">
39+
40+
Enable "Configure client access" checkbox as shown in the screenshot.
41+
42+
<Frame>
43+
<img src="/images/oci/ocieditdomainsettings.png" alt="OCI IAM Domain Settings" />
44+
</Frame>
45+
</Step>
46+
</Steps>
47+
48+
### Step 2: Create OAuth client for MCP server authentication
49+
50+
Follow the Steps as mentioned below to create an OAuth client.
51+
52+
<Steps>
53+
<Step title="Navigate to OCI IAM Integrated Applications">
54+
55+
Login to OCI console (https://cloud.oracle.com for OCI commercial cloud).
56+
From "Identity & Security" menu, open Domains page.
57+
On the Domains list page, select the domain in which you want to create MCP server OAuth client. If you need help finding the list page for the domain, see [Listing Identity Domains.](https://docs.oracle.com/en-us/iaas/Content/Identity/domains/to-view-identity-domains.htm#view-identity-domains).
58+
On the details page, select Integrated applications. A list of applications in the domain is displayed.
59+
</Step>
60+
61+
<Step title="Add an Integrated Application">
62+
63+
Select Add application.
64+
In the Add application window, select Confidential Application.
65+
Select Launch workflow.
66+
In the Add application details page, Enter name and description as shown below.
67+
68+
<Frame>
69+
<img src="/images/oci/ociaddapplication.png" alt="Adding a Confidential Integrated Application in OCI IAM Domain" />
70+
</Frame>
71+
</Step>
72+
73+
<Step title="Update OAuth Configuration for an Integrated Application">
74+
75+
Once the Integrated Application is created, Click on "OAuth configuration" tab.
76+
Click on "Edit OAuth configuration" button.
77+
Configure the application as OAuth client by selecting "Configure this application as a client now" radio button.
78+
Select "Authorization code" grant type. If you are planning to use the same OAuth client application for token exchange, select "Client credentials" grant type as well. In the sample, we will use the same client.
79+
For Authorization grant type, select redirect URL. In most cases, this will be the MCP server URL followed by "/oauth/callback".
80+
81+
<Frame>
82+
<img src="/images/oci/ocioauthconfiguration.png" alt="OAuth Configuration for an Integrated Application in OCI IAM Domain" />
83+
</Frame>
84+
</Step>
85+
86+
<Step title="Activate the Integrated Application">
87+
88+
Click on "Submit" button to update OAuth configuration for the client application.
89+
**Note: You don't need to do any special configuration to support PKCE for the OAuth client.**
90+
Make sure to Activate the client application.
91+
Note down client ID and client secret for the application. Update .env file and replace FASTMCP_SERVER_AUTH_OCI_CLIENT_ID and FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET values.
92+
FASTMCP_SERVER_AUTH_OCI_IAM_GUID in the env file is the Identity domain URL that you chose for the MCP server.
93+
</Step>
94+
</Steps>
95+
96+
This is all you need to implement MCP server authentication against OCI IAM. However, you may want to use an authenticated user token to invoke OCI control plane APIs and propagate identity to the OCI control plane instead of using a service user account. In that case, you need to implement token exchange.
97+
98+
### Step 3: Token Exchange Setup (Only if MCP server needs to talk to OCI Control Plane)
99+
100+
Token exchange helps you exchange a logged-in user's OCI IAM token for an OCI control plane session token, also known as UPST (User Principal Session Token). To learn more about token exchange, refer to my [Workload Identity Federation Blog](https://www.ateam-oracle.com/post/workload-identity-federation)
101+
102+
For token exchange, we need to configure Identity propagation trust. The blog above discusses setting up the trust using REST APIs. However, you can also use OCI CLI. Before using the CLI command below, ensure that you have created a token exchange OAuth client. In most cases, you can use the same OAuth client that you created above. You will use the client ID of the token exchange OAuth client in the CLI command below and replace it with {FASTMCP_SERVER_AUTH_OCI_CLIENT_ID}.
103+
104+
You will also need to update the client secret for the token exchange OAuth client in the .env file. It is the FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET parameter. Update FASTMCP_SERVER_AUTH_OCI_IAM_GUID and FASTMCP_SERVER_AUTH_OCI_CLIENT_ID as well for the token exchange OAuth client in the .env file.
105+
106+
```bash
107+
oci identity-domains identity-propagation-trust create \
108+
--schemas '["urn:ietf:params:scim:schemas:oracle:idcs:IdentityPropagationTrust"]' \
109+
--public-key-endpoint "https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oraclecloud.com/admin/v1/SigningCert/jwk" \
110+
--name "For Token Exchange" --type "JWT" \
111+
--issuer "https://identity.oraclecloud.com/" --active true \
112+
--endpoint "https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oracleclcoud.com" \
113+
--subject-claim-name "sub" --allow-impersonation false \
114+
--subject-mapping-attribute "username" \
115+
--subject-type "User" --client-claim-name "iss" \
116+
--client-claim-values '["https://identity.oraclecloud.com/"]' \
117+
--oauth-clients '["{FASTMCP_SERVER_AUTH_OCI_CLIENT_ID}"]'
118+
```
119+
120+
To exchange access token for OCI token and create a signer object, you need to add below code in MCP server. You can then use the signer object to create any OCI control plane client.
121+
122+
```python
123+
124+
from fastmcp.server.dependencies import get_access_token
125+
from fastmcp.utilities.logging import get_logger
126+
from oci.auth.signers import TokenExchangeSigner
127+
import os
128+
129+
logger = get_logger(__name__)
130+
131+
# Load configuration from environment
132+
FASTMCP_SERVER_AUTH_OCI_IAM_GUID = os.environ["FASTMCP_SERVER_AUTH_OCI_IAM_GUID"]
133+
FASTMCP_SERVER_AUTH_OCI_CLIENT_ID = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"]
134+
FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"]
135+
136+
_global_token_cache = {} #In memory cache for OCI session token signer
137+
138+
def get_oci_signer() -> TokenExchangeSigner:
139+
140+
authntoken = get_access_token()
141+
tokenID = authntoken.claims.get("jti")
142+
token = authntoken.token
143+
144+
#Check if the signer exists for the token ID in memory cache
145+
cached_signer = _global_token_cache.get(tokenID)
146+
logger.debug(f"Global cached signer: {cached_signer}")
147+
if cached_signer:
148+
logger.debug(f"Using globally cached signer for token ID: {tokenID}")
149+
return cached_signer
150+
151+
#If the signer is not yet created for the token then create new OCI signer object
152+
logger.debug(f"Creating new signer for token ID: {tokenID}")
153+
signer = TokenExchangeSigner(
154+
jwt_or_func=token,
155+
oci_domain_id=FASTMCP_SERVER_AUTH_OCI_IAM_GUID.split(".")[0],
156+
client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID,
157+
client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET,
158+
)
159+
logger.debug(f"Signer {signer} created for token ID: {tokenID}")
160+
161+
#Cache the signer object in memory cache
162+
_global_token_cache[tokenID] = signer
163+
logger.debug(f"Signer cached for token ID: {tokenID}")
164+
165+
return signer
166+
```
167+
168+
## Running MCP server
169+
170+
Once the setup is complete, to run the MCP server, run the below command.
171+
```bash
172+
fastmcp run server.py:mcp --transport http --port 8000
173+
```
174+
175+
To run MCP client, run the below command.
176+
```bash
177+
python3 client.py
178+
```
179+
180+
MCP Client sample is as below.
181+
```python client.py
182+
from fastmcp import Client
183+
import asyncio
184+
185+
async def main():
186+
# The client will automatically handle OCI OAuth flows
187+
async with Client("http://localhost:8000/mcp/", auth="oauth") as client:
188+
# First-time connection will open OCI login in your browser
189+
print("✓ Authenticated with OCI IAM")
190+
191+
tools = await client.list_tools()
192+
print(f"🔧 Available tools ({len(tools)}):")
193+
for tool in tools:
194+
print(f" - {tool.name}: {tool.description}")
195+
196+
if __name__ == "__main__":
197+
asyncio.run(main())
198+
```
199+
200+
When you run the client for the first time:
201+
1. Your browser will open to OCI IAM's login page
202+
2. Sign in with your OCI account and grant the requested consent
203+
3. After authorization, you'll be redirected back to the redirect path
204+
4. The client receives the token and can make authenticated requests
205+
206+
## Production Configuration
207+
208+
<VersionBadge version="2.13.0" />
209+
210+
For production deployments with persistent token management across server restarts, configure `jwt_signing_key`, and `client_storage`:
211+
212+
```python server.py
213+
214+
import os
215+
from fastmcp import FastMCP
216+
from fastmcp.server.auth.providers.oci import OCIProvider
217+
218+
from key_value.aio.stores.redis import RedisStore
219+
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
220+
from cryptography.fernet import Fernet
221+
222+
# Load configuration from environment
223+
FASTMCP_SERVER_AUTH_OCI_CONFIG_URL = os.environ["FASTMCP_SERVER_AUTH_OCI_CONFIG_URL"]
224+
FASTMCP_SERVER_AUTH_OCI_CLIENT_ID = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"]
225+
FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET = os.environ["FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"]
226+
227+
# Production setup with encrypted persistent token storage
228+
auth_provider = OCIProvider(
229+
config_url=FASTMCP_SERVER_AUTH_OCI_CONFIG_URL,
230+
client_id=FASTMCP_SERVER_AUTH_OCI_CLIENT_ID,
231+
client_secret=FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET,
232+
base_url="https://your-production-domain.com",
233+
234+
# Production token management
235+
jwt_signing_key=os.environ["JWT_SIGNING_KEY"],
236+
client_storage=FernetEncryptionWrapper(
237+
key_value=RedisStore(
238+
host=os.environ["REDIS_HOST"],
239+
port=int(os.environ["REDIS_PORT"])
240+
),
241+
fernet=Fernet(os.environ["STORAGE_ENCRYPTION_KEY"])
242+
)
243+
)
244+
245+
mcp = FastMCP(name="Production OCI App", auth=auth_provider)
246+
```
247+
248+
<Note>
249+
Parameters (`jwt_signing_key` and `client_storage`) work together to ensure tokens and client registrations survive server restarts. **Wrap your storage in `FernetEncryptionWrapper` to encrypt sensitive OAuth tokens at Rest** - without it, tokens are stored in plaintext. Store secrets in environment variables and use a persistent storage backend like Redis for distributed deployments.
250+
251+
For complete details on these parameters, see the [OAuth Proxy documentation](/servers/auth/oauth-proxy#configuration-parameters).
252+
</Note>
253+
254+
<Info>
255+
The client caches tokens locally, so you won't need to re-authenticate for subsequent runs unless the token expires or you explicitly clear the cache.
256+
</Info>
257+
258+
## Environment Variables
259+
260+
For production deployments, use environment variables instead of hardcoding credentials.
261+
262+
### Provider Selection
263+
264+
Setting this environment variable allows the OCI provider to be used automatically without explicitly instantiating it in code.
265+
266+
<Card>
267+
<ParamField path="FASTMCP_SERVER_AUTH" default="Not set">
268+
Set to `fastmcp.server.auth.providers.oci.OCIProvider` to use OCI IAM authentication.
269+
</ParamField>
270+
</Card>
271+
272+
### OCI-Specific Configuration
273+
274+
These environment variables provide default values for the OCI IAM provider, whether it's instantiated manually or configured via `FASTMCP_SERVER_AUTH`.
275+
276+
<Card>
277+
<ParamField path="FASTMCP_SERVER_AUTH_OCI_IAM_GUID" required>
278+
Your OCI Application Configuration URL (e.g., `idcs-asdascxasd11......identity.oraclecloud.com`)
279+
</ParamField>
280+
281+
<ParamField path="FASTMCP_SERVER_AUTH_OCI_CONFIG_URL" required>
282+
Your OCI Application Configuration URL (e.g., `https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oraclecloud.com/.well-known/openid-configuration`)
283+
</ParamField>
284+
285+
<ParamField path="FASTMCP_SERVER_AUTH_OCI_CLIENT_ID" required>
286+
Your OCI Application Client ID (e.g., `tv2ObNgaZAWWhhycr7Bz1LU2mxlnsmsB`)
287+
</ParamField>
288+
289+
<ParamField path="FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET" required>
290+
Your OCI Application Client Secret (e.g., `idcsssvPYqbjemq...`)
291+
</ParamField>
292+
293+
<ParamField path="FASTMCP_SERVER_AUTH_OCI_BASE_URL" required>
294+
Public URL where OAuth endpoints will be accessible (includes any mount path)
295+
</ParamField>
296+
297+
<ParamField path="FASTMCP_SERVER_AUTH_OCI_REDIRECT_PATH" default="/auth/callback">
298+
Redirect path configured in your OCI IAM Integrated Application
299+
</ParamField>
300+
301+
</Card>
302+
303+
Example `.env` file:
304+
```bash
305+
# Use the OCI IAM provider
306+
FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.oci.OCIProvider
307+
308+
# OCI IAM configuration and credentials
309+
FASTMCP_SERVER_AUTH_OCI_IAM_GUID=idcs-asaacasd1111.....
310+
FASTMCP_SERVER_AUTH_OCI_CONFIG_URL=https://{FASTMCP_SERVER_AUTH_OCI_IAM_GUID}.identity.oraclecloud.com/.well-known/openid-configuration
311+
FASTMCP_SERVER_AUTH_OCI_CLIENT_ID=<your-client-id>
312+
FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET=<your-client-secret>
313+
FASTMCP_SERVER_AUTH_OCI_BASE_URL=https://your-server.com
314+
```
315+
316+
With environment variables set, your server code simplifies to:
317+
318+
```python server.py
319+
from fastmcp import FastMCP
320+
from fastmcp.server.dependencies import get_access_token
321+
322+
# Authentication is automatically configured from environment
323+
mcp = FastMCP(name="OCI Secured App")
324+
325+
@mcp.tool
326+
def whoami() -> str:
327+
"""The whoami function is to test MCP server without requiring token exchange.
328+
This tool can be used to test successful authentication against OCI IAM.
329+
It will return logged in user's subject (username from IAM domain)."""
330+
token = get_access_token()
331+
user = token.claims.get("sub")
332+
return f"You are User: {user}"
333+
```

0 commit comments

Comments
 (0)