-
Notifications
You must be signed in to change notification settings - Fork 531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Support PKCE #2435
feat: Support PKCE #2435
Conversation
@jskeet I will ad a few tests to this etc. but just wanted to check with you that this approach looks good. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I basically follow this. Various comments, but nothing huge.
/// The redirect URI for the authorization code request. | ||
/// </param> | ||
/// <param name="codeVerifier"> | ||
/// The code verifier associated to the code challenge that should be included |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe highlight that this is an out parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/// in the returned <see cref="AuthorizationCodeRequestUrl"/>. | ||
/// </param> | ||
/// <returns>An <see cref="AuthorizationCodeRequestUrl"/> subclass instance that includes the code challenge | ||
/// and code challange method associated to <paramref name="codeVerifier"/>.</returns> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
challange => challenge and possibly "associated to" => "associated with"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, both.
/// and code challange method associated to <paramref name="codeVerifier"/>.</returns> | ||
AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri, out string codeVerifier); | ||
|
||
/// <summary>Asynchronously exchanges code with a token.</summary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"code with a token" => "an authorization code for an access token"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done here, and from where I copied this.
/// <param name="code">Authorization code received from the authorization server.</param> | ||
/// <param name="codeVerifier"> | ||
/// The PKCE code verifier to send along the exchange request. | ||
/// You obtained the code verifier when calling <see cref="CreateAuthorizationCodeRequest(string, out string)"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's revisit this sentence when the rest is ready to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'm adding a TODO so we don't forget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, how about:
The PKCE code verifier to include in the exchange request. When called by the authentication library, this will be the same value specified by the <code>codeVerifier</code> out parameter in an earlier call to CreateAuthorizationCodeRequest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, better, done.
@@ -0,0 +1,62 @@ | |||
/* | |||
Copyright 2013 Google Inc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2023, here and below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
{ | ||
byte[] codeVerifierAsciiBytes = Encoding.ASCII.GetBytes(codeVerifier); | ||
byte[] hashedCodeVerifier; | ||
using (var sha256 = SHA256.Create()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplify the using statement? (That would allow us to declare hashedCodeVerifier at the point of assignment.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, done
request.CodeChallengeMethod = effectiveChallengeMethod; | ||
} | ||
|
||
private static string Base64UrlEncode(byte[] data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just checking that we don't have this anywhere else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually we do, and it did ring a bell when I wrote it.
private static string GenerateCodeVerifier() | ||
{ | ||
byte[] data = new byte[CodeVerifierRandomNumberLengthBytes]; | ||
using(RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: space after using
, or better, let's just use a simplified using statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, just we really don't need it until the end, but that shouldn't be a problem really.
/// The scopes which indicate the Google API access your application is requesting. | ||
/// </param> | ||
/// <param name="user">The user to authorize.</param> | ||
/// <param name="disablePkce"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to have usePkce
if possible - 1) it avoids the term "disable", and 2) it's more positive. I'm okay with this if it's for consistency with other languages' already-released packages though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's fine, I'll swap it.
My intention was mostly to make it explicit that not using PKCE should be the exemption.
@@ -86,8 +103,8 @@ public class GoogleAuthorizationCodeRequestUrl : AuthorizationCodeRequestUrl | |||
/// The name of this parameter is used only for the constructor and will not end up in the resultant query | |||
/// string. | |||
/// </remarks> | |||
[Google.Apis.Util.RequestParameterAttribute("user_defined_query_params", | |||
Google.Apis.Util.RequestParameterType.UserDefinedQueries)] | |||
[RequestParameter("user_defined_query_params", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unwrap line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All comments addressed and tests added.
I've also split in 4 commits to make it easier to review.
@jskeet PTAL, thanks!
@@ -0,0 +1,62 @@ | |||
/* | |||
Copyright 2013 Google Inc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/// The redirect URI for the authorization code request. | ||
/// </param> | ||
/// <param name="codeVerifier"> | ||
/// The code verifier associated to the code challenge that should be included |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/// in the returned <see cref="AuthorizationCodeRequestUrl"/>. | ||
/// </param> | ||
/// <returns>An <see cref="AuthorizationCodeRequestUrl"/> subclass instance that includes the code challenge | ||
/// and code challange method associated to <paramref name="codeVerifier"/>.</returns> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, both.
/// and code challange method associated to <paramref name="codeVerifier"/>.</returns> | ||
AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri, out string codeVerifier); | ||
|
||
/// <summary>Asynchronously exchanges code with a token.</summary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done here, and from where I copied this.
/// <param name="code">Authorization code received from the authorization server.</param> | ||
/// <param name="codeVerifier"> | ||
/// The PKCE code verifier to send along the exchange request. | ||
/// You obtained the code verifier when calling <see cref="CreateAuthorizationCodeRequest(string, out string)"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'm adding a TODO so we don't forget.
{ | ||
byte[] codeVerifierAsciiBytes = Encoding.ASCII.GetBytes(codeVerifier); | ||
byte[] hashedCodeVerifier; | ||
using (var sha256 = SHA256.Create()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, done
effectiveChallenge = Base64UrlEncode(hashedCodeVerifier); | ||
effectiveChallengeMethod = ChallengeMethodSha256; | ||
} | ||
catch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, there's this one case on .NET 4.61, but I'll be more explicit about the exception: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha256.create
request.CodeChallengeMethod = effectiveChallengeMethod; | ||
} | ||
|
||
private static string Base64UrlEncode(byte[] data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually we do, and it did ring a bell when I wrote it.
/// The scopes which indicate the Google API access your application is requesting. | ||
/// </param> | ||
/// <param name="user">The user to authorize.</param> | ||
/// <param name="disablePkce"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's fine, I'll swap it.
My intention was mostly to make it explicit that not using PKCE should be the exemption.
@@ -86,8 +103,8 @@ public class GoogleAuthorizationCodeRequestUrl : AuthorizationCodeRequestUrl | |||
/// The name of this parameter is used only for the constructor and will not end up in the resultant query | |||
/// string. | |||
/// </remarks> | |||
[Google.Apis.Util.RequestParameterAttribute("user_defined_query_params", | |||
Google.Apis.Util.RequestParameterType.UserDefinedQueries)] | |||
[RequestParameter("user_defined_query_params", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
/// <param name="code">Authorization code received from the authorization server.</param> | ||
/// <param name="codeVerifier"> | ||
/// The PKCE code verifier to send along the exchange request. | ||
/// You obtained the code verifier when calling <see cref="CreateAuthorizationCodeRequest(string, out string)"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, how about:
The PKCE code verifier to include in the exchange request. When called by the authentication library, this will be the same value specified by the <code>codeVerifier</code> out parameter in an earlier call to CreateAuthorizationCodeRequest.
using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create(); | ||
randomNumberGenerator.GetBytes(data); | ||
|
||
// The code verifier alphabet does not allow +, / or =, so we Base64URL encode. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's more that base64-URL-encoding is simply how the code verifier is specified in the RFC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, true, it just doesn't say so anywhere in our docs, just what the alphabet is, so I unconsciously followed along. Changing.
effectiveChallenge = TokenEncodingHelpers.UrlSafeBase64Encode(hashedCodeVerifier); | ||
effectiveChallengeMethod = ChallengeMethodSha256; | ||
} | ||
catch (TargetInvocationException) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly add a comment here as you did in the GitHub conversation to explain why this might happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, done.
This PKCE flow is not used in this commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments addressed, thanks!
I'll merge on green.
/// <param name="code">Authorization code received from the authorization server.</param> | ||
/// <param name="codeVerifier"> | ||
/// The PKCE code verifier to send along the exchange request. | ||
/// You obtained the code verifier when calling <see cref="CreateAuthorizationCodeRequest(string, out string)"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, better, done.
using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create(); | ||
randomNumberGenerator.GetBytes(data); | ||
|
||
// The code verifier alphabet does not allow +, / or =, so we Base64URL encode. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, true, it just doesn't say so anywhere in our docs, just what the alphabet is, so I unconsciously followed along. Changing.
effectiveChallenge = TokenEncodingHelpers.UrlSafeBase64Encode(hashedCodeVerifier); | ||
effectiveChallengeMethod = ChallengeMethodSha256; | ||
} | ||
catch (TargetInvocationException) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, done.
@amanda-tarafa this is great, thanks! |
Updates support version: 1.61.0 - beta02 -> 1.61.0 Features - Improvements for date/time parsing. - googleapis#2441 - googleapis#2432 - googleapis#2429 - googleapis#2435 PKCE support. - googleapis#2394 Which improves impersonation support. - googleapis#2349 and googleapis#2379 Which improve error reported when ADC is not configured.
FYI: @clundin25