Skip to content

Commit a711fc3

Browse files
committed
feat(backend): enhance OAuth2 authorization with default scopes and resource
1 parent 9e09491 commit a711fc3

File tree

1 file changed

+39
-11
lines changed

1 file changed

+39
-11
lines changed

services/backend/src/routes/oauth2/authorization.ts

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import {
1414
type OAuth2ErrorResponse
1515
} from './schemas';
1616

17+
// Default scopes when client doesn't provide scope parameter
18+
// This matches scopes_supported in discovery endpoints
19+
const DEFAULT_OAUTH_SCOPES = 'mcp:read mcp:tools:execute offline_access';
20+
1721
// Reusable Schema Constants
1822
const AUTHORIZATION_QUERY_SCHEMA = {
1923
type: 'object',
@@ -32,17 +36,21 @@ const AUTHORIZATION_QUERY_SCHEMA = {
3236
},
3337
scope: {
3438
...SCOPE_SCHEMA,
35-
description: 'Space-separated list of requested scopes'
39+
description: 'Space-separated list of requested scopes (optional, defaults to all supported scopes)'
3640
},
3741
state: STATE_SCHEMA,
3842
code_challenge: CODE_CHALLENGE_SCHEMA,
3943
code_challenge_method: CODE_CHALLENGE_METHOD_SCHEMA,
4044
team: {
4145
type: 'string',
4246
description: 'Team ID for team-scoped OAuth flow (optional, defaults to user team)'
47+
},
48+
resource: {
49+
type: 'string',
50+
description: 'RFC 8707 Resource Indicator - target MCP server URI (optional, used for audience binding)'
4351
}
4452
},
45-
required: ['response_type', 'client_id', 'redirect_uri', 'scope', 'state', 'code_challenge', 'code_challenge_method'],
53+
required: ['response_type', 'client_id', 'redirect_uri', 'state', 'code_challenge', 'code_challenge_method'],
4654
additionalProperties: false
4755
} as const;
4856

@@ -51,11 +59,12 @@ interface AuthorizationQuery {
5159
response_type: 'code';
5260
client_id: string;
5361
redirect_uri: string;
54-
scope: string;
62+
scope?: string;
5563
state: string;
5664
code_challenge: string;
5765
code_challenge_method: 'S256';
5866
team?: string;
67+
resource?: string;
5968
}
6069

6170
export default async function authorizationRoute(server: FastifyInstance) {
@@ -70,15 +79,16 @@ export default async function authorizationRoute(server: FastifyInstance) {
7079
properties: {
7180
client_id: { type: 'string' },
7281
redirect_uri: { type: 'string' },
73-
scope: { type: 'string' },
82+
scope: { type: 'string', description: 'Optional, defaults to all supported scopes' },
7483
state: { type: 'string' },
7584
code_challenge: { type: 'string' },
7685
code_challenge_method: { type: 'string' },
7786
response_type: { type: 'string' },
7887
team_id: { type: 'string' },
79-
consent: { type: 'string', enum: ['true', 'false'] }
88+
consent: { type: 'string', enum: ['true', 'false'] },
89+
resource: { type: 'string', description: 'RFC 8707 Resource Indicator (optional)' }
8090
},
81-
required: ['client_id', 'redirect_uri', 'scope', 'state', 'code_challenge', 'code_challenge_method', 'response_type', 'team_id', 'consent'],
91+
required: ['client_id', 'redirect_uri', 'state', 'code_challenge', 'code_challenge_method', 'response_type', 'team_id', 'consent'],
8292
additionalProperties: false
8393
},
8494
response: {
@@ -94,17 +104,22 @@ export default async function authorizationRoute(server: FastifyInstance) {
94104
}
95105
}, async (request, reply) => {
96106
try {
97-
const { client_id, redirect_uri, scope, state, code_challenge, code_challenge_method, response_type, team_id, consent } = request.body as {
107+
const body = request.body as {
98108
client_id: string;
99109
redirect_uri: string;
100-
scope: string;
110+
scope?: string;
101111
state: string;
102112
code_challenge: string;
103113
code_challenge_method: string;
104114
response_type: string;
105115
team_id: string;
106116
consent: string;
117+
resource?: string;
107118
};
119+
const { client_id, redirect_uri, state, code_challenge, code_challenge_method, response_type, team_id, consent } = body;
120+
121+
// Use default scopes if not provided
122+
const scope = body.scope || DEFAULT_OAUTH_SCOPES;
108123

109124
// Check user authentication
110125
if (!request.user) {
@@ -242,16 +257,29 @@ export default async function authorizationRoute(server: FastifyInstance) {
242257
}
243258
}, async (request, reply) => {
244259
try {
260+
const query = request.query as AuthorizationQuery;
245261
const {
246262
response_type,
247263
client_id,
248264
redirect_uri,
249-
scope,
250265
state,
251266
code_challenge,
252267
code_challenge_method,
253-
team
254-
} = request.query as AuthorizationQuery;
268+
team,
269+
resource
270+
} = query;
271+
272+
// Use default scopes if not provided (MCP clients may not send scope)
273+
const scope = query.scope || DEFAULT_OAUTH_SCOPES;
274+
275+
if (!query.scope) {
276+
request.log.debug({
277+
operation: 'oauth2_authorization',
278+
clientId: client_id,
279+
defaultScope: scope,
280+
resource,
281+
}, 'No scope provided, using default MCP scopes');
282+
}
255283

256284
// Check if user is authenticated first
257285
if (!request.user) {

0 commit comments

Comments
 (0)