Skip to content

Commit d45ef03

Browse files
committed
feat: Okta MCP auth docs
Signed-off-by: John McBride <[email protected]>
1 parent 1e931bd commit d45ef03

File tree

3 files changed

+357
-0
lines changed

3 files changed

+357
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
---
2+
title: Setting up Okta as an Authentication Server for MCP OAuth Authentication
3+
---
4+
5+
In this guide, you'll learn how to configure Okta as an authorization server
6+
for use with the MCP Server handler. See the
7+
[MCP Server Handler docs](../handlers/mcp-server.md#oauth-authentication) for
8+
instructions on how to configure your Zuplo gateway to support OAuth
9+
authentication for your MCP Server.
10+
11+
This guide will assume that you already have a working Okta account and organization.
12+
13+
## Create an Auth Server
14+
15+
First, you will need to create an Okta authorization server.
16+
This server will be used to authorize requests to your MCP Server per
17+
[the Model Context Protocol authorization specification.](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)
18+
19+
1. In the Okta Admin Console, navigate to **Security > API** in the left sidebar.
20+
2. Click **Add Authorization Server**.
21+
3. Set the **Name** to something like "MCP Server Authorization".
22+
4. Set the **Audience** to the canonical URL of your MCP Server. For example, if your MCP Server is hosted
23+
at `https://my-gateway.zuplo.dev/mcp`, then the audience would be
24+
`https://my-gateway.zuplo.dev/mcp`. **The trailing slash is not required.**
25+
5. Add a **Description** and click **Save**.
26+
27+
Note the **Issuer Metadata URI** shown in the authorization server details.
28+
You'll need this for your Zuplo configuration.
29+
30+
## Configure Scopes
31+
32+
Next, you'll need to configure the scopes for your authorization server.
33+
34+
1. In your authorization server settings, click the **Scopes** tab.
35+
2. Click **Add Scope**.
36+
3. Set the **Name** to something like `mcp:access`.
37+
4. Add a **Display phrase** and **Description** (like "Access to MCP Server tools").
38+
5. Check **Set as a default scope** and click **Create**.
39+
40+
## Create an OAuth Client Application
41+
42+
Next, you'll need to create an OAuth client application for your MCP server.
43+
44+
:::note
45+
46+
Okta requires an admin API key for to dynamically register clients.
47+
This may not be well supported by MCP clients.
48+
However, MCP clients should also support an alternative way to obtain a client ID and client credential.
49+
This document assumes an MCP client can set these fields without having to dynamically register a client.
50+
51+
:::
52+
53+
1. In the Okta Admin Console, navigate to **Applications > Applications** in the left sidebar.
54+
2. Click **Create App Integration**.
55+
3. Select **OIDC - OpenID Connect** as the sign-in method.
56+
4. Select **Web Application** as the application type and click **Next**.
57+
5. Set the **App integration name** to something like "MCP Client Application".
58+
6. For **Grant types**, check **Authorization Code** and **Refresh Token**.
59+
7. For **Sign-in redirect URIs**, leave this empty or set to a placeholder like `http://localhost:3000/callback`.
60+
8. For **Controlled access**, select **Allow everyone in your organization to access**.
61+
9. Click **Save**.
62+
63+
After creating the application, note the **Client ID** and **Client Secret**
64+
from the application's **General** tab. You'll need these for your MCP client configuration.
65+
66+
## Create a Default Policy and Rule
67+
68+
You'll need to create an access policy for your authorization server.
69+
70+
1. In your authorization server settings (found in **Security > API**) click the **Access Policies** tab.
71+
2. Click **Add New Access Policy**.
72+
3. Set the **Name** to something like "MCP Client Access Policy".
73+
4. Add a **Description** and assign it to **All clients**.
74+
5. Click **Create Policy**.
75+
76+
Now create a rule for this policy:
77+
78+
1. Click **Add Rule** within your new policy.
79+
2. Set the **Rule Name** to something like "Allow MCP Access".
80+
3. In the **IF AND** section:
81+
- **Grant type is**: Select the grant type. For the widest grant for all MCP clients, select **Client Credentials**, **Authorization Code**, and **Device Authorization** (???)
82+
- **User is**: Select **Any user assigned the app**
83+
- **Scopes requested**: Select **The following scopes** and choose the scope you created for the authorization server (i.e., `mcp:access`)
84+
4. In the **THEN AND** section:
85+
- **Use this inline hook**: None (disabled)
86+
- **Access token lifetime is**: Set to desired value (e.g., 1 hour)
87+
- **Refresh token lifetime is**: Set to desired value (e.g., 90 days)
88+
5. Click **Create Rule**.
89+
90+
## Configure OAuth on Zuplo
91+
92+
To set up your gateway to support OAuth authentication for your MCP Server, you
93+
will need to do the following:
94+
95+
1. Create an Okta JWT Auth inbound policy on your MCP Server route. This policy will need to
96+
have the option `"oAuthResourceMetadataEnabled": true` to enable authorization resource metadata discovery.
97+
98+
```json
99+
{
100+
"name": "mcp-okta-oauth-inbound",
101+
"policyType": "okta-jwt-auth-inbound",
102+
"handler": {
103+
"export": "OktaJwtInboundPolicy",
104+
"module": "$import(@zuplo/runtime)",
105+
"options": {
106+
"oAuthResourceMetadataEnabled": true,
107+
"audience": "https://my-gateway.zuplo.dev/mcp",
108+
"issuer": "https://your-okta-domain.okta.com/oauth2/your-auth-server-id",
109+
}
110+
}
111+
}
112+
```
113+
114+
* Replace `my-gateway.zuplo.dev/mcp` with the audience you defined in your authorization server.
115+
* Replace `your-okta-domain` in the `issuer` field with your actual Okta domain.
116+
* Replace `your-auth-server-id` in the `issuer` field with the actual ID of your Okta authorization server.
117+
118+
2. Add the OAuth policy to the MCP Server route. For example:
119+
120+
```json
121+
"paths": {
122+
"/mcp": {
123+
"post": {
124+
"x-zuplo-route": {
125+
126+
// etc. etc.
127+
// other properties for the MCP server route handler
128+
129+
"policies": {
130+
"inbound": [
131+
"mcp-oauth-inbound"
132+
]
133+
}
134+
}
135+
}
136+
}
137+
}
138+
```
139+
140+
3. Add the `OAuthProtectedResourcePlugin` to your `runtimeInit` function in the
141+
`modules/zuplo.runtime.ts` file:
142+
143+
```ts
144+
import {
145+
RuntimeExtensions,
146+
OAuthProtectedResourcePlugin,
147+
} from "@zuplo/runtime";
148+
149+
export function runtimeInit(runtime: RuntimeExtensions) {
150+
runtime.addPlugin(
151+
new OAuthProtectedResourcePlugin({
152+
authorizationServers: [ "https://your-okta-domain.okta.com/oauth2/your-auth-server-id" ],
153+
resourceName: "My MCP OAuth Resource",
154+
}),
155+
);
156+
}
157+
```
158+
159+
* Replace `your-okta-domain` in the `issuer` field with your actual Okta domain.
160+
* Replace `your-auth-server-id` in the `issuer` field with the actual ID of your Okta authorization server.
161+
162+
This plugin populates the `.well-known` routes for the MCP server auth metadata discover.
163+
This enables MCP clients to automatically discover the authorization issuer endpoint.
164+
See the
165+
[OAuth Protected Resource Plugin docs](../programmable-api/oauth-protected-resource-plugin)
166+
for more details on this runtime plugin.
167+
168+
## Testing
169+
170+
The [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
171+
doesn't currently support setting an initial access token or presenting a UI for
172+
setting the client ID or secret.
173+
174+
Refer to the [Manual OAuth MCP Testing](./manual-mcp-oauth-testing) guide for
175+
further instructions on testing your MCP server with `curl`.
176+
177+
If you need more help debugging, see
178+
[Testing OAuth on Zuplo](../handlers/mcp-server.md#oauth-testing).
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
title: Manual OAuth MCP Testing
3+
---
4+
5+
MCP client OAuth support is evolving rapidly and many clients don't yet support
6+
the full flow for dynamic client registration, PKCE, initial tokens during DCR, etc.
7+
8+
This guide shows you how you can use common tools like `curl` and `openssl`
9+
to manually test a configured authorization server and MCP server.
10+
11+
## Existing auth server client ID and secret
12+
13+
The following guide walks you through manually testing an OAuth provider with a
14+
client ID and secret configuration.
15+
This is also useful for debugging OAuth issues during authorization flows for MCP.
16+
17+
### Prerequisites
18+
19+
- `curl`, `jq`, and `openssl` installed on your system
20+
- An OAuth client ID and client Secret. For the purposes of demonstration,
21+
this guide uses an Okta Application client ID and secret.
22+
23+
### Testing Script
24+
25+
Create a test script to verify your complete OAuth flow:
26+
27+
```bash
28+
#!/bin/bash
29+
30+
# OAuth 2.1 flow test Script for MCP with pre-configured client.
31+
# Based on MCP Authorization specification:
32+
# https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization
33+
34+
set -e
35+
36+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
37+
# !!!!!!! Configuration !!!!!!!!
38+
#
39+
# Update these values to your MCP server hosted on Zuplo and your auth provider
40+
# config values.
41+
42+
MCP_ENDPOINT="https://your-gateway.zuplo.dev/mcp"
43+
CLIENT_ID="your-client-id"
44+
CLIENT_SECRET="your-client-secret"
45+
REDIRECT_URI="http://localhost:8080/authorization-code/callback"
46+
SCOPE="mcp:access"
47+
48+
# !!!!! Configuration end !!!!!!
49+
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
50+
51+
# Colors for pretty output
52+
RED='\033[0;31m'
53+
GREEN='\033[0;32m'
54+
BLUE='\033[0;34m'
55+
YELLOW='\033[1;33m'
56+
NC='\033[0m'
57+
58+
echo -e "${BLUE}=== MCP OAuth 2.1 Testing ===${NC}"
59+
60+
# Step 1: Discover OAuth configuration from MCP endpoint
61+
echo -e "${BLUE}Step 1: Discovering OAuth configuration...${NC}"
62+
MCP_RESPONSE=$(curl -s -i -X POST "${MCP_ENDPOINT}" \
63+
-H 'content-type: application/json' \
64+
-d '{"jsonrpc": "2.0", "id": "1", "method": "ping"}')
65+
66+
# Extract resource metadata URL
67+
RESOURCE_METADATA_URL=$(echo "${MCP_RESPONSE}" | grep -i "resource_metadata=" | \
68+
sed 's/.*resource_metadata=\([^[:space:]]*\).*/\1/' | tr -d '\r\n')
69+
70+
echo "Resource metadata URL: ${RESOURCE_METADATA_URL}"
71+
72+
# Step 2: Get authorization server info
73+
RESOURCE_METADATA=$(curl -s "${RESOURCE_METADATA_URL}")
74+
AUTH_SERVER_URL=$(echo "${RESOURCE_METADATA}" | jq -r '.authorization_servers[0]')
75+
76+
echo "Authorization Server: ${AUTH_SERVER_URL}"
77+
78+
# Step 3: Get OAuth endpoints
79+
OAUTH_METADATA=$(curl -s "${AUTH_SERVER_URL}/.well-known/oauth-authorization-server")
80+
AUTH_ENDPOINT=$(echo "${OAUTH_METADATA}" | jq -r '.authorization_endpoint')
81+
TOKEN_ENDPOINT=$(echo "${OAUTH_METADATA}" | jq -r '.token_endpoint')
82+
83+
# Step 4: Generate PKCE parameters
84+
CODE_VERIFIER=$(openssl rand -base64 32 | tr '/+' '_-' | tr -d '=')
85+
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '/+' '_-' | tr -d '=')
86+
STATE=$(openssl rand -hex 16)
87+
88+
# Step 5: Build authorization URL
89+
AUTH_URL="${AUTH_ENDPOINT}?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${SCOPE}&state=${STATE}&code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256"
90+
91+
echo -e "${YELLOW}Open this URL in your browser:${NC}"
92+
echo "${AUTH_URL}"
93+
echo
94+
echo -e "${YELLOW}After login, copy the authorization code from the callback URL${NC}"
95+
read -p "Enter the authorization code: " AUTH_CODE
96+
97+
# Step 6: Exchange code for token
98+
TOKEN_RESPONSE=$(curl -s -X POST "${TOKEN_ENDPOINT}" \
99+
-H "Content-Type: application/x-www-form-urlencoded" \
100+
-u "${CLIENT_ID}:${CLIENT_SECRET}" \
101+
-d "grant_type=authorization_code&code=${AUTH_CODE}&redirect_uri=${REDIRECT_URI}&code_verifier=${CODE_VERIFIER}")
102+
103+
ACCESS_TOKEN=$(echo "${TOKEN_RESPONSE}" | jq -r '.access_token')
104+
105+
if [[ "$ACCESS_TOKEN" == "null" ]]; then
106+
echo -e "${RED}Token exchange failed:${NC}"
107+
echo "${TOKEN_RESPONSE}" | jq .
108+
exit 1
109+
fi
110+
111+
echo -e "${GREEN}✓ Access token obtained${NC}"
112+
113+
# Step 7: Test MCP endpoint with token
114+
MCP_AUTH_RESPONSE=$(curl -s -X POST "${MCP_ENDPOINT}" \
115+
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
116+
-H 'Accept: application/json, text/event-stream' \
117+
-d '{"jsonrpc": "2.0", "id": "1", "method": "ping"}')
118+
119+
echo -e "${GREEN}✓ MCP ping test:${NC}"
120+
echo "${MCP_AUTH_RESPONSE}" | jq .
121+
122+
echo -e "${GREEN}=== OAuth flow test complete ===${NC}"
123+
```
124+
125+
### Running the Test
126+
127+
1. **Update the configuration** at the top of the script with your values:
128+
- `MCP_ENDPOINT`: Your Zuplo gateway MCP endpoint
129+
- `CLIENT_ID`: The ID of the client or application for the auth server
130+
- `CLIENT_SECRET`: The secret for the client or application for the auth server
131+
- `REDIRECT_URI`: Typically, must match exactly what's configured in your auth server's redirect
132+
133+
2. **Make the script executable**: `chmod +x test-oauth.sh`
134+
135+
3. **Run the script**: `./test-oauth.sh`
136+
137+
4. **Follow the prompts**:
138+
- Open the authorization URL in your browser from the terminal output
139+
- Complete the login flow
140+
- Copy the authorization code from the callback URL. It will look something like:
141+
142+
```
143+
http://localhost:8080/authorization-code/callback?code=ABC123&state=XYZ987
144+
```
145+
146+
- Paste just the code part into the script
147+
148+
:::warning
149+
150+
This script does **not** include a web server and does **not** support
151+
dynamically extracting the authorization callback code.
152+
You must manually grab the code from the query parameter
153+
and enter it into the terminal prompt.
154+
155+
:::
156+
157+
### Common Issues and Troubleshooting
158+
159+
**"Policy evaluation failed"**: Check that your client has:
160+
- Correct redirect URI configured
161+
- Correct scope assigned and configured
162+
- Authorization code grant enabled
163+
- Proper access policies and rules
164+
165+
**"Invalid client" errors**: Verify your Client ID and Client Secret are correct.
166+
167+
**"Invalid redirect URI"**: The redirect URI configured in the script may need to exactly match what's configured in your authorization server and client.
168+
169+
**Browser redirects immediately without login**: You may already be logged into your auth provider or an existing session exists.
170+
To log in from a clean state, try the following:
171+
- Log out from your authorization provider
172+
- Clear browser cookies, sessions, and caches
173+
- Using an incognito/private browser window
174+
175+
**Token exchange fails**: Check that:
176+
- PKCE is enabled for your client
177+
- The authorization code hasn't expired (use it immediately)

sidebar.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ export const docs: Navigation = [
226226
"articles/non-standard-ports",
227227
"articles/convert-urls-to-openapi",
228228
"articles/configuring-auth0-for-mcp-auth",
229+
"articles/configuring-okta-for-mcp-auth",
230+
"articles/manual-mcp-oauth-testing"
229231
],
230232
},
231233
{

0 commit comments

Comments
 (0)