@@ -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 ,
0 commit comments