Skip to content

Commit a90d01e

Browse files
committed
Consolidate some of the PKCE logic.
1 parent 9209b61 commit a90d01e

File tree

3 files changed

+83
-118
lines changed

3 files changed

+83
-118
lines changed

src/ModelContextProtocol/Auth/AuthorizationHandlers.cs

Lines changed: 0 additions & 79 deletions
This file was deleted.

src/ModelContextProtocol/Auth/OAuthAuthenticationService.cs

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,31 @@ public class OAuthAuthenticationService
1616
private static readonly HttpClient _httpClient = new();
1717
private readonly Func<Uri, Task<string>>? _authorizationHandler;
1818

19+
/// <summary>
20+
/// Represents the PKCE code challenge and verifier for an authorization flow.
21+
/// </summary>
22+
public class PkceValues
23+
{
24+
/// <summary>
25+
/// The code verifier used to generate the code challenge.
26+
/// </summary>
27+
public string CodeVerifier { get; }
28+
29+
/// <summary>
30+
/// The code challenge sent to the authorization server.
31+
/// </summary>
32+
public string CodeChallenge { get; }
33+
34+
/// <summary>
35+
/// Initializes a new instance of the <see cref="PkceValues"/> class.
36+
/// </summary>
37+
public PkceValues(string codeVerifier, string codeChallenge)
38+
{
39+
CodeVerifier = codeVerifier;
40+
CodeChallenge = codeChallenge;
41+
}
42+
}
43+
1944
/// <summary>
2045
/// Initializes a new instance of the <see cref="OAuthAuthenticationService"/> class.
2146
/// </summary>
@@ -32,6 +57,59 @@ public OAuthAuthenticationService(Func<Uri, Task<string>> authorizationHandler)
3257
_authorizationHandler = authorizationHandler ?? throw new ArgumentNullException(nameof(authorizationHandler));
3358
}
3459

60+
/// <summary>
61+
/// Generates new PKCE values.
62+
/// </summary>
63+
/// <returns>A <see cref="PkceValues"/> instance containing the code verifier and challenge.</returns>
64+
public static PkceValues GeneratePkceValues()
65+
{
66+
var codeVerifier = GenerateCodeVerifier();
67+
var codeChallenge = GenerateCodeChallenge(codeVerifier);
68+
return new PkceValues(codeVerifier, codeChallenge);
69+
}
70+
71+
/// <summary>
72+
/// Generates a cryptographically random code verifier for PKCE.
73+
/// </summary>
74+
/// <returns>A base64url encoded string to be used as the code verifier.</returns>
75+
public static string GenerateCodeVerifier()
76+
{
77+
// Generate a cryptographically random code verifier
78+
var bytes = new byte[64];
79+
using var rng = RandomNumberGenerator.Create();
80+
rng.GetBytes(bytes);
81+
82+
// Base64url encode the random bytes
83+
var base64 = Convert.ToBase64String(bytes);
84+
var base64Url = base64
85+
.Replace('+', '-')
86+
.Replace('/', '_')
87+
.Replace("=", "");
88+
89+
return base64Url;
90+
}
91+
92+
/// <summary>
93+
/// Generates a code challenge from a code verifier using the S256 method.
94+
/// </summary>
95+
/// <param name="codeVerifier">The code verifier to generate the challenge from.</param>
96+
/// <returns>A base64url encoded SHA256 hash of the code verifier.</returns>
97+
public static string GenerateCodeChallenge(string codeVerifier)
98+
{
99+
// Create code challenge using S256 method
100+
using var sha256 = SHA256.Create();
101+
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
102+
103+
// Base64url encode the hash
104+
var base64 = Convert.ToBase64String(challengeBytes);
105+
var base64Url = base64
106+
.Replace('+', '-')
107+
.Replace('/', '_')
108+
.Replace("=", "");
109+
110+
return base64Url;
111+
}
112+
35113
/// <summary>
36114
/// Handles the OAuth authentication flow when a 401 Unauthorized response is received.
37115
/// </summary>
@@ -251,16 +329,15 @@ private async Task<OAuthToken> PerformAuthorizationCodeFlowAsync(
251329
IEnumerable<string> scopes,
252330
Func<Uri, Task<string>>? authorizationHandler)
253331
{
254-
// Generate PKCE code verifier and challenge
255-
var codeVerifier = GenerateCodeVerifier();
256-
var codeChallenge = GenerateCodeChallenge(codeVerifier);
332+
// Generate PKCE values using our public method
333+
var pkceValues = GeneratePkceValues();
257334

258335
// Build authorization URL
259336
var authorizationUrl = BuildAuthorizationUrl(
260337
authServerMetadata.AuthorizationEndpoint,
261338
clientId,
262339
redirectUri,
263-
codeChallenge,
340+
pkceValues.CodeChallenge,
264341
scopes);
265342

266343
// Check if an authorization handler is available
@@ -278,7 +355,7 @@ private async Task<OAuthToken> PerformAuthorizationCodeFlowAsync(
278355
clientSecret,
279356
redirectUri,
280357
authorizationCode,
281-
codeVerifier);
358+
pkceValues.CodeVerifier);
282359
}
283360
catch (Exception ex)
284361
{
@@ -293,39 +370,6 @@ private async Task<OAuthToken> PerformAuthorizationCodeFlowAsync(
293370
$"You need to handle this redirect and extract the authorization code to complete the flow.");
294371
}
295372

296-
private string GenerateCodeVerifier()
297-
{
298-
// Generate a cryptographically random code verifier
299-
var bytes = new byte[64];
300-
using var rng = RandomNumberGenerator.Create();
301-
rng.GetBytes(bytes);
302-
303-
// Base64url encode the random bytes
304-
var base64 = Convert.ToBase64String(bytes);
305-
var base64Url = base64
306-
.Replace('+', '-')
307-
.Replace('/', '_')
308-
.Replace("=", "");
309-
310-
return base64Url;
311-
}
312-
313-
private string GenerateCodeChallenge(string codeVerifier)
314-
{
315-
// Create code challenge using S256 method
316-
using var sha256 = SHA256.Create();
317-
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
318-
319-
// Base64url encode the hash
320-
var base64 = Convert.ToBase64String(challengeBytes);
321-
var base64Url = base64
322-
.Replace('+', '-')
323-
.Replace('/', '_')
324-
.Replace("=", "");
325-
326-
return base64Url;
327-
}
328-
329373
private string BuildAuthorizationUrl(
330374
Uri authorizationEndpoint,
331375
string clientId,

src/ModelContextProtocol/Auth/Types/AuthorizationCodeOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class AuthorizationCodeOptions
3838
/// <summary>
3939
/// PKCE values for the authorization flow.
4040
/// </summary>
41-
public PkceUtility.PkceValues PkceValues { get; set; } = null!;
41+
public OAuthAuthenticationService.PkceValues PkceValues { get; set; } = null!;
4242

4343
/// <summary>
4444
/// A state value to protect against CSRF attacks.

0 commit comments

Comments
 (0)