-
Notifications
You must be signed in to change notification settings - Fork 531
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5f6c410
commit d675d5e
Showing
7 changed files
with
318 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
Src/Support/Google.Apis.Auth/OAuth2/Flows/IPkceAuthorizationCodeFlow.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
Copyright 2013 Google Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
using Google.Apis.Auth.OAuth2.Requests; | ||
using Google.Apis.Auth.OAuth2.Responses; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Google.Apis.Auth.OAuth2.Flows | ||
{ | ||
/// <summary> | ||
/// Authorization flow that supports Proof Key for Code Exchange (PKCE) | ||
/// as described in https://www.rfc-editor.org/rfc/rfc7636. | ||
/// </summary> | ||
/// <remarks> | ||
/// If you are writing your own authorization flow to be used with <see cref="AuthorizationCodeInstalledApp"/> | ||
/// make sure you implement this interface if you need to support PKCE. | ||
/// See https://developers.google.com/identity/protocols/oauth2/native-app for how Google supports PKCE. | ||
/// </remarks> | ||
public interface IPkceAuthorizationCodeFlow : IAuthorizationCodeFlow | ||
{ | ||
/// <summary> | ||
/// Creates an authorization code request with the specified redirect URI. | ||
/// </summary> | ||
/// <param name="redirectUri"> | ||
/// The redirect URI for the authorization code request. | ||
/// </param> | ||
/// <param name="codeVerifier"> | ||
/// The code verifier associated to the code challenge that should be included | ||
/// 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> | ||
AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri, out string codeVerifier); | ||
|
||
/// <summary>Asynchronously exchanges code with a token.</summary> | ||
/// <param name="userId">User identifier.</param> | ||
/// <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)"/> | ||
/// for creating the authorization code request that yielded <paramref name="code"/>. | ||
/// </param> | ||
/// <param name="redirectUri">Redirect URI which is used in the token request.</param> | ||
/// <param name="taskCancellationToken">Cancellation token to cancel operation.</param> | ||
/// <returns>Token response which contains the access token.</returns> | ||
Task<TokenResponse> ExchangeCodeForTokenAsync(string userId, string code, string codeVerifier, string redirectUri, CancellationToken taskCancellationToken); | ||
} | ||
} |
116 changes: 116 additions & 0 deletions
116
Src/Support/Google.Apis.Auth/OAuth2/Flows/PkceGoogleAuthorizationCodeFlow.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
Copyright 2013 Google Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
using Google.Apis.Auth.OAuth2.Requests; | ||
using Google.Apis.Auth.OAuth2.Responses; | ||
using System; | ||
using System.Security.Cryptography; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Google.Apis.Auth.OAuth2.Flows | ||
{ | ||
/// <summary> | ||
/// Google authorization flow implementation that supports PKCE as described in https://www.rfc-editor.org/rfc/rfc7636 | ||
/// and https://developers.google.com/identity/protocols/oauth2/native-app. | ||
/// </summary> | ||
public class PkceGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow, IPkceAuthorizationCodeFlow | ||
{ | ||
private const int CodeVerifierMaxLengthChars = 128; | ||
// We generate the random bytes and then encode them to Base64 whose characters are 6 bits long. | ||
private const int CodeVerifierRandomNumberLengthBytes = CodeVerifierMaxLengthChars * 6 / 8; | ||
private const string ChallengeMethodSha256 = "S256"; | ||
private const string ChallengeMethodPlain = "plain"; | ||
|
||
/// <summary> | ||
/// Creates a new instance from the given initializer. | ||
/// </summary> | ||
public PkceGoogleAuthorizationCodeFlow(Initializer initializer) : base(initializer) | ||
{ | ||
} | ||
|
||
/// <inheritdoc/> | ||
public AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri, out string codeVerifier) | ||
{ | ||
var request = CreateAuthorizationCodeRequest(redirectUri) as GoogleAuthorizationCodeRequestUrl; | ||
codeVerifier = GenerateCodeVerifier(); | ||
AddCodeChallenge(request, codeVerifier); | ||
return request; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public async Task<TokenResponse> ExchangeCodeForTokenAsync(string userId, string code, string codeVerifier, string redirectUri, CancellationToken taskCancellationToken) | ||
{ | ||
var request = CreateAuthorizationCodeTokenRequest(userId, code, redirectUri); | ||
request.CodeVerifier = codeVerifier; | ||
|
||
return await ExchangeCodeForTokenAsync(userId, request, taskCancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
private static string GenerateCodeVerifier() | ||
{ | ||
byte[] data = new byte[CodeVerifierRandomNumberLengthBytes]; | ||
using(RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) | ||
{ | ||
randomNumberGenerator.GetBytes(data); | ||
} | ||
|
||
// The code verifier alphabet does not allow +, / or =, so we Base64URL encode. | ||
// https://developers.google.com/identity/protocols/oauth2/native-app#step1-code-verifier | ||
return Base64UrlEncode(data); | ||
} | ||
|
||
private static void AddCodeChallenge(GoogleAuthorizationCodeRequestUrl request, string codeVerifier) | ||
{ | ||
// https://developers.google.com/identity/protocols/oauth2/native-app#step1-code-verifier | ||
string effectiveChallenge; | ||
string effectiveChallengeMethod; | ||
try | ||
{ | ||
byte[] codeVerifierAsciiBytes = Encoding.ASCII.GetBytes(codeVerifier); | ||
byte[] hashedCodeVerifier; | ||
using (var sha256 = SHA256.Create()) | ||
{ | ||
hashedCodeVerifier = sha256.ComputeHash(codeVerifierAsciiBytes); | ||
} | ||
effectiveChallenge = Base64UrlEncode(hashedCodeVerifier); | ||
effectiveChallengeMethod = ChallengeMethodSha256; | ||
} | ||
catch | ||
{ | ||
effectiveChallenge = codeVerifier; | ||
effectiveChallengeMethod = ChallengeMethodPlain; | ||
} | ||
|
||
request.CodeChallenge = effectiveChallenge; | ||
request.CodeChallengeMethod = effectiveChallengeMethod; | ||
} | ||
|
||
private static string Base64UrlEncode(byte[] data) | ||
{ | ||
StringBuilder codeVerifierBuilder = new StringBuilder(Convert.ToBase64String(data)); | ||
// Remove = padding. | ||
while (codeVerifierBuilder[codeVerifierBuilder.Length - 1] == '=') | ||
{ | ||
codeVerifierBuilder.Length--; | ||
} | ||
codeVerifierBuilder.Replace('+', '-'); | ||
codeVerifierBuilder.Replace('/', '_'); | ||
return codeVerifierBuilder.ToString(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.