Skip to content

Commit 21f0efa

Browse files
committed
Add resource parameter to /token and /refresh requests
1 parent 363754b commit 21f0efa

3 files changed

Lines changed: 22 additions & 11 deletions

File tree

src/ModelContextProtocol.AspNetCore/Authentication/McpAuthenticationExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ public static AuthenticationBuilder AddMcp(
4242
return builder.AddScheme<McpAuthenticationOptions, McpAuthenticationHandler>(
4343
authenticationScheme,
4444
displayName,
45-
configureOptions); // No-op to avoid overriding
45+
configureOptions);
4646
}
4747
}

src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.Logging;
22
using Microsoft.Extensions.Logging.Abstractions;
3+
using System.Collections.Specialized;
34
using System.Diagnostics.CodeAnalysis;
45
using System.Security.Cryptography;
56
using System.Text;
@@ -127,8 +128,6 @@ public ClientOAuthProvider(
127128
{
128129
ThrowIfNotBearerScheme(scheme);
129130

130-
// REVIEW: Should we be doing anything with the resourceUri? If not, why is it part of the IMcpCredentialProvider interface?
131-
132131
// Return the token if it's valid
133132
if (_token != null && _token.ExpiresAt > DateTimeOffset.UtcNow.AddMinutes(5))
134133
{
@@ -138,7 +137,7 @@ public ClientOAuthProvider(
138137
// Try to refresh the token if we have a refresh token
139138
if (_token?.RefreshToken != null && _authServerMetadata != null)
140139
{
141-
var newToken = await RefreshTokenAsync(_token.RefreshToken, _authServerMetadata, cancellationToken).ConfigureAwait(false);
140+
var newToken = await RefreshTokenAsync(_token.RefreshToken, resourceUri, _authServerMetadata, cancellationToken).ConfigureAwait(false);
142141
if (newToken != null)
143142
{
144143
_token = newToken;
@@ -276,15 +275,15 @@ private async Task PerformOAuthAuthorizationAsync(
276275
return null;
277276
}
278277

279-
private async Task<TokenContainer> RefreshTokenAsync(string refreshToken, AuthorizationServerMetadata authServerMetadata, CancellationToken cancellationToken)
278+
private async Task<TokenContainer> RefreshTokenAsync(string refreshToken, Uri resourceUri, AuthorizationServerMetadata authServerMetadata, CancellationToken cancellationToken)
280279
{
281280
var requestContent = new FormUrlEncodedContent(new Dictionary<string, string>
282281
{
283282
["grant_type"] = "refresh_token",
284283
["refresh_token"] = refreshToken,
285284
["client_id"] = GetClientIdOrThrow(),
286285
["client_secret"] = _clientSecret ?? string.Empty,
287-
// Add resource
286+
["resource"] = resourceUri.ToString(),
288287
});
289288

290289
using var request = new HttpRequestMessage(HttpMethod.Post, authServerMetadata.TokenEndpoint)
@@ -311,7 +310,7 @@ private async Task<TokenContainer> RefreshTokenAsync(string refreshToken, Author
311310
return null;
312311
}
313312

314-
return await ExchangeCodeForTokenAsync(authServerMetadata, authCode!, codeVerifier, cancellationToken).ConfigureAwait(false);
313+
return await ExchangeCodeForTokenAsync(protectedResourceMetadata, authServerMetadata, authCode!, codeVerifier, cancellationToken).ConfigureAwait(false);
315314
}
316315

317316
private Uri BuildAuthorizationUrl(
@@ -326,7 +325,7 @@ private Uri BuildAuthorizationUrl(
326325
}
327326

328327
var queryParams = HttpUtility.ParseQueryString(string.Empty);
329-
queryParams["client_id"] = _clientId;
328+
queryParams["client_id"] = GetClientIdOrThrow();
330329
queryParams["redirect_uri"] = _redirectUri.ToString();
331330
queryParams["response_type"] = "code";
332331
queryParams["code_challenge"] = codeChallenge;
@@ -348,6 +347,7 @@ private Uri BuildAuthorizationUrl(
348347
}
349348

350349
private async Task<TokenContainer> ExchangeCodeForTokenAsync(
350+
ProtectedResourceMetadata protectedResourceMetadata,
351351
AuthorizationServerMetadata authServerMetadata,
352352
string authorizationCode,
353353
string codeVerifier,
@@ -361,7 +361,7 @@ private async Task<TokenContainer> ExchangeCodeForTokenAsync(
361361
["client_id"] = GetClientIdOrThrow(),
362362
["code_verifier"] = codeVerifier,
363363
["client_secret"] = _clientSecret ?? string.Empty,
364-
// TODO: Add resource
364+
["resource"] = protectedResourceMetadata.Resource.ToString(),
365365
});
366366

367367
using var request = new HttpRequestMessage(HttpMethod.Post, authServerMetadata.TokenEndpoint)

tests/ModelContextProtocol.TestOAuthServer/Program.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ public async Task RunServerAsync(string[]? args = null, CancellationToken cancel
221221

222222
// Generate a new authorization code
223223
var code = OAuthUtils.GenerateRandomToken();
224-
var requestedScopes = scope?.Split(' ').ToList() ?? new List<string>();
224+
var requestedScopes = scope?.Split(' ').ToList() ?? [];
225225

226226
// Store code information for later verification
227227
_authCodes[code] = new AuthorizationCodeInfo
@@ -247,7 +247,6 @@ public async Task RunServerAsync(string[]? args = null, CancellationToken cancel
247247
app.MapPost("/token", async (HttpContext context) =>
248248
{
249249
var form = await context.Request.ReadFormAsync();
250-
var grant_type = form["grant_type"].ToString();
251250

252251
// Authenticate client
253252
var client = AuthenticateClient(context, form);
@@ -261,6 +260,18 @@ public async Task RunServerAsync(string[]? args = null, CancellationToken cancel
261260
type: "https://tools.ietf.org/html/rfc6749#section-5.2");
262261
}
263262

263+
// Validate resource in accordance with RFC 8707
264+
var resource = form["resource"].ToString();
265+
if (string.IsNullOrEmpty(resource) || !ValidResources.Contains(resource))
266+
{
267+
return Results.BadRequest(new OAuthErrorResponse
268+
{
269+
Error = "invalid_target",
270+
ErrorDescription = "The specified resource is not valid."
271+
});
272+
}
273+
274+
var grant_type = form["grant_type"].ToString();
264275
if (grant_type == "authorization_code")
265276
{
266277
var code = form["code"].ToString();

0 commit comments

Comments
 (0)