Skip to content

Commit aebb814

Browse files
author
Lasim
committed
feat(satellite): add support for public backend URL in OAuth configuration
1 parent 2fa3257 commit aebb814

File tree

3 files changed

+32
-16
lines changed

3 files changed

+32
-16
lines changed

services/satellite/.env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ LOG_LEVEL=debug
66
# Backend Connection Configuration
77
DEPLOYSTACK_BACKEND_URL=http://localhost:3000
88

9+
# Backend Public URL for OAuth (optional)
10+
# Use when satellite connects to backend via private network but MCP clients
11+
# need to reach backend via public internet URL.
12+
#
13+
# Example split-network setup:
14+
# - DEPLOYSTACK_BACKEND_URL=http://10.0.0.3:8080 (internal/private network)
15+
# - DEPLOYSTACK_BACKEND_PUBLIC_URL=https://cloud.deploystack.io (external/public)
16+
#
17+
# If not set, defaults to DEPLOYSTACK_BACKEND_URL (works for simple deployments)
18+
DEPLOYSTACK_BACKEND_PUBLIC_URL=
19+
920
# Satellite Public URL Configuration (REQUIRED for production OAuth)
1021
# This is the publicly accessible URL of this satellite instance
1122
# Used for OAuth 2.0 Protected Resource Metadata (RFC 9728)

services/satellite/src/middleware/auth-middleware.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,12 @@ export function requireScope(requiredScope: string) {
173173
*/
174174
function sendAuthenticationRequired(reply: FastifyReply, _request: FastifyRequest) {
175175
const backendUrl = process.env.DEPLOYSTACK_BACKEND_URL || 'http://localhost:3000';
176-
176+
// Public URL for OAuth - what MCP clients will use to reach the backend
177+
const backendPublicUrl = process.env.DEPLOYSTACK_BACKEND_PUBLIC_URL || backendUrl;
178+
177179
const wwwAuthenticate = `Bearer realm="DeployStack MCP Satellite", ` +
178-
`authorizationUri="${backendUrl}/api/oauth2/auth", ` +
179-
`tokenUri="${backendUrl}/api/oauth2/token", ` +
180+
`authorizationUri="${backendPublicUrl}/api/oauth2/auth", ` +
181+
`tokenUri="${backendPublicUrl}/api/oauth2/token", ` +
180182
`resource="deploystack:mcp:satellite"`;
181183

182184
const errorResponse = {
@@ -186,8 +188,8 @@ function sendAuthenticationRequired(reply: FastifyReply, _request: FastifyReques
186188
message: 'Authentication required',
187189
data: {
188190
message: 'Bearer token required for MCP access',
189-
authorization_uri: `${backendUrl}/api/oauth2/auth`,
190-
token_uri: `${backendUrl}/api/oauth2/token`
191+
authorization_uri: `${backendPublicUrl}/api/oauth2/auth`,
192+
token_uri: `${backendPublicUrl}/api/oauth2/token`
191193
}
192194
},
193195
id: null

services/satellite/src/routes/oauth-discovery.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { type FastifyInstance } from 'fastify';
77
*/
88
export default async function oauthDiscoveryRoutes(server: FastifyInstance) {
99
const backendUrl = process.env.DEPLOYSTACK_BACKEND_URL || 'http://localhost:3000';
10+
// Public URL for OAuth metadata - what MCP clients will use to reach the backend
11+
// Falls back to backendUrl for simple deployments where internal = external URL
12+
const backendPublicUrl = process.env.DEPLOYSTACK_BACKEND_PUBLIC_URL || backendUrl;
1013
const satelliteUrl = process.env.DEPLOYSTACK_SATELLITE_URL || `http://localhost:${process.env.PORT || 3001}`;
1114

1215
// RFC 9728: OAuth 2.0 Protected Resource Metadata
@@ -31,7 +34,7 @@ export default async function oauthDiscoveryRoutes(server: FastifyInstance) {
3134
}, async (request, reply) => {
3235
const metadata = {
3336
resource: satelliteUrl,
34-
authorization_servers: [backendUrl]
37+
authorization_servers: [backendPublicUrl]
3538
};
3639

3740
server.log.debug({
@@ -81,15 +84,15 @@ export default async function oauthDiscoveryRoutes(server: FastifyInstance) {
8184
}
8285
}, async (request, reply) => {
8386
const metadata = {
84-
issuer: backendUrl,
85-
authorization_endpoint: `${backendUrl}/api/oauth2/auth`,
86-
token_endpoint: `${backendUrl}/api/oauth2/token`,
87-
introspection_endpoint: `${backendUrl}/api/oauth2/introspect`,
87+
issuer: backendPublicUrl,
88+
authorization_endpoint: `${backendPublicUrl}/api/oauth2/auth`,
89+
token_endpoint: `${backendPublicUrl}/api/oauth2/token`,
90+
introspection_endpoint: `${backendPublicUrl}/api/oauth2/introspect`,
8891
response_types_supported: ['code'],
8992
grant_types_supported: ['authorization_code'],
9093
code_challenge_methods_supported: ['S256'],
9194
scopes_supported: ['mcp:read', 'mcp:tools:execute', 'offline_access'],
92-
registration_endpoint: `${backendUrl}/api/oauth2/register`
95+
registration_endpoint: `${backendPublicUrl}/api/oauth2/register`
9396
};
9497

9598
server.log.debug({
@@ -145,15 +148,15 @@ export default async function oauthDiscoveryRoutes(server: FastifyInstance) {
145148
}, async (request, reply) => {
146149
// Return the same metadata as OAuth authorization server for compatibility
147150
const metadata = {
148-
issuer: backendUrl,
149-
authorization_endpoint: `${backendUrl}/api/oauth2/auth`,
150-
token_endpoint: `${backendUrl}/api/oauth2/token`,
151-
introspection_endpoint: `${backendUrl}/api/oauth2/introspect`,
151+
issuer: backendPublicUrl,
152+
authorization_endpoint: `${backendPublicUrl}/api/oauth2/auth`,
153+
token_endpoint: `${backendPublicUrl}/api/oauth2/token`,
154+
introspection_endpoint: `${backendPublicUrl}/api/oauth2/introspect`,
152155
response_types_supported: ['code'],
153156
grant_types_supported: ['authorization_code'],
154157
code_challenge_methods_supported: ['S256'],
155158
scopes_supported: ['mcp:read', 'mcp:tools:execute', 'offline_access'],
156-
registration_endpoint: `${backendUrl}/api/oauth2/register`
159+
registration_endpoint: `${backendPublicUrl}/api/oauth2/register`
157160
};
158161

159162
server.log.debug({

0 commit comments

Comments
 (0)