Skip to content

Commit 87956d4

Browse files
committed
Add CanAuthenticate_WithTokenRefresh test
1 parent ee2f76b commit 87956d4

2 files changed

Lines changed: 56 additions & 2 deletions

File tree

tests/ModelContextProtocol.AspNetCore.Tests/AuthTests.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class AuthTests : KestrelInMemoryTest, IAsyncDisposable
1919
private const string OAuthServerUrl = "https://localhost:7029";
2020

2121
private readonly CancellationTokenSource _testCts = new();
22+
private readonly TestOAuthServer.Program TestOAuthServer;
2223
private readonly Task _oAuthRunTask;
2324

2425
public AuthTests(ITestOutputHelper outputHelper)
@@ -30,8 +31,8 @@ public AuthTests(ITestOutputHelper outputHelper)
3031
// The easiest workaround is to disable cert validation for testing purposes.
3132
SocketsHttpHandler.SslOptions.RemoteCertificateValidationCallback = (_, _, _, _) => true;
3233

33-
var oAuthServerProgram = new TestOAuthServer.Program(XunitLoggerProvider, KestrelInMemoryTransport);
34-
_oAuthRunTask = oAuthServerProgram.RunServerAsync(cancellationToken: _testCts.Token);
34+
TestOAuthServer = new TestOAuthServer.Program(XunitLoggerProvider, KestrelInMemoryTransport);
35+
_oAuthRunTask = TestOAuthServer.RunServerAsync(cancellationToken: _testCts.Token);
3536

3637
Builder.Services.AddAuthentication(options =>
3738
{
@@ -189,6 +190,37 @@ public async Task CanAuthenticate_WithDynamicClientRegistration()
189190
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
190191
}
191192

193+
[Fact]
194+
public async Task CanAuthenticate_WithTokenRefresh()
195+
{
196+
Builder.Services.AddMcpServer().WithHttpTransport();
197+
198+
await using var app = Builder.Build();
199+
200+
app.MapMcp().RequireAuthorization();
201+
202+
await app.StartAsync(TestContext.Current.CancellationToken);
203+
204+
await using var transport = new SseClientTransport(new()
205+
{
206+
Endpoint = new(McpServerUrl),
207+
OAuth = new()
208+
{
209+
ClientId = "test-refresh-client",
210+
ClientSecret = "test-refresh-secret",
211+
RedirectUri = new Uri("http://localhost:1179/callback"),
212+
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
213+
},
214+
}, HttpClient, LoggerFactory);
215+
216+
// The test-refresh-client should get an expired token first,
217+
// then automatically refresh it to get a working token
218+
await using var client = await McpClientFactory.CreateAsync(
219+
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
220+
221+
Assert.True(TestOAuthServer.HasIssuedRefreshToken);
222+
}
223+
192224
[Fact]
193225
public void CloneResourceMetadataClonesAllProperties()
194226
{

tests/ModelContextProtocol.TestOAuthServer/Program.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ public Program(ILoggerProvider? loggerProvider = null, IConnectionListenerFactor
3939
_kestrelTransport = kestrelTransport;
4040
}
4141

42+
// Track if we've already issued an already-expired token for the CanAuthenticate_WithTokenRefresh test which uses the test-refresh-client registration.
43+
private bool HasIssuedExpiredToken { get; set; }
44+
public bool HasIssuedRefreshToken { get; set; }
45+
4246
/// <summary>
4347
/// Entry point for the application.
4448
/// </summary>
@@ -104,6 +108,15 @@ public async Task RunServerAsync(string[]? args = null, CancellationToken cancel
104108
RedirectUris = ["http://localhost:1179/callback"],
105109
};
106110

111+
// When this client ID is used, the first token issued will already be expired to make
112+
// testing the refresh flow easier.
113+
_clients["test-refresh-client"] = new ClientInfo
114+
{
115+
ClientId = "test-refresh-client",
116+
ClientSecret = "test-refresh-secret",
117+
RedirectUris = ["http://localhost:1179/callback"],
118+
};
119+
107120
// OIDC and OAuth Metadata
108121
app.MapGet("/.well-known/openid-configuration", () =>
109122
{
@@ -345,6 +358,7 @@ public async Task RunServerAsync(string[]? args = null, CancellationToken cancel
345358
_tokens.TryRemove(refresh_token, out _);
346359
}
347360

361+
HasIssuedRefreshToken = true;
348362
return Results.Ok(response);
349363
}
350364
else
@@ -508,6 +522,14 @@ private TokenResponse GenerateJwtTokenResponse(string clientId, List<string> sco
508522
{
509523
var expiresIn = TimeSpan.FromHours(1);
510524
var issuedAt = DateTimeOffset.UtcNow;
525+
526+
// For test-refresh-client, make the first token expired to test refresh functionality.
527+
if (clientId == "test-refresh-client" && !HasIssuedExpiredToken)
528+
{
529+
HasIssuedExpiredToken = true;
530+
expiresIn = TimeSpan.FromHours(-1);
531+
}
532+
511533
var expiresAt = issuedAt.Add(expiresIn);
512534
var jwtId = Guid.NewGuid().ToString();
513535

0 commit comments

Comments
 (0)