🐛 Remove PKCE on Google OIDC

This commit is contained in:
LittleSheep 2025-06-17 23:08:58 +08:00
parent ada84f85e9
commit 5c02c63f70
2 changed files with 9 additions and 63 deletions

View File

@ -278,7 +278,7 @@ public class ConnectionController(
} }
// Clean up and redirect // Clean up and redirect
var returnUrlKey = $"{ReturnUrlCachePrefix}{CleanStateCodeVerifier(callbackData.State)}"; var returnUrlKey = $"{ReturnUrlCachePrefix}{callbackData.State}";
var returnUrl = await cache.GetAsync<string>(returnUrlKey); var returnUrl = await cache.GetAsync<string>(returnUrlKey);
await cache.RemoveAsync(returnUrlKey); await cache.RemoveAsync(returnUrlKey);
@ -367,9 +367,4 @@ public class ConnectionController(
return data; return data;
} }
private static string? CleanStateCodeVerifier(string? og)
{
return og is null ? null : !og.Contains('|') ? og : og.Split('|').First();
}
} }

View File

@ -7,9 +7,6 @@ using Microsoft.IdentityModel.Tokens;
namespace DysonNetwork.Sphere.Auth.OpenId; namespace DysonNetwork.Sphere.Auth.OpenId;
/// <summary>
/// Implementation of OpenID Connect service for Google Sign In
/// </summary>
public class GoogleOidcService( public class GoogleOidcService(
IConfiguration configuration, IConfiguration configuration,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
@ -34,13 +31,7 @@ public class GoogleOidcService(
throw new InvalidOperationException("Authorization endpoint not found in discovery document"); throw new InvalidOperationException("Authorization endpoint not found in discovery document");
} }
// Generate code verifier and challenge for PKCE // PKCE code removed: no code verifier/challenge generated
var codeVerifier = GenerateCodeVerifier();
var codeChallenge = GenerateCodeChallenge(codeVerifier);
// Store code verifier in session or cache for later use
// For simplicity, we'll append it to the state parameter in this example
var combinedState = $"{state}|{codeVerifier}";
var queryParams = new Dictionary<string, string> var queryParams = new Dictionary<string, string>
{ {
@ -48,10 +39,8 @@ public class GoogleOidcService(
{ "redirect_uri", config.RedirectUri }, { "redirect_uri", config.RedirectUri },
{ "response_type", "code" }, { "response_type", "code" },
{ "scope", "openid email profile" }, { "scope", "openid email profile" },
{ "state", combinedState }, { "state", state }, // No '|codeVerifier' appended anymore
{ "nonce", nonce }, { "nonce", nonce }
{ "code_challenge", codeChallenge },
{ "code_challenge_method", "S256" }
}; };
var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}"));
@ -60,20 +49,13 @@ public class GoogleOidcService(
public override async Task<OidcUserInfo> ProcessCallbackAsync(OidcCallbackData callbackData) public override async Task<OidcUserInfo> ProcessCallbackAsync(OidcCallbackData callbackData)
{ {
// Extract code verifier from state // No need to split or parse code verifier from state
string? codeVerifier = null;
var state = callbackData.State ?? ""; var state = callbackData.State ?? "";
callbackData.State = state; // Keep the original state if needed
if (state.Contains('|'))
{
var parts = state.Split('|');
state = parts[0];
codeVerifier = parts.Length > 1 ? parts[1] : null;
callbackData.State = state; // Set the clean state back
}
// Exchange the code for tokens // Exchange the code for tokens
var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code, codeVerifier); // Pass null or omit the parameter for codeVerifier as PKCE is removed
var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code, null);
if (tokenResponse?.IdToken == null) if (tokenResponse?.IdToken == null)
{ {
throw new InvalidOperationException("Failed to obtain ID token from Google"); throw new InvalidOperationException("Failed to obtain ID token from Google");
@ -101,7 +83,6 @@ public class GoogleOidcService(
if (userInfoResponse != null) if (userInfoResponse != null)
{ {
// Extract any additional fields that might be available
if (userInfoResponse.TryGetValue("picture", out var picture) && picture != null) if (userInfoResponse.TryGetValue("picture", out var picture) && picture != null)
{ {
userInfo.ProfilePictureUrl = picture.ToString(); userInfo.ProfilePictureUrl = picture.ToString();
@ -109,7 +90,7 @@ public class GoogleOidcService(
} }
} }
} }
catch (Exception) catch
{ {
// Ignore errors when fetching additional profile data // Ignore errors when fetching additional profile data
} }
@ -125,7 +106,6 @@ public class GoogleOidcService(
throw new InvalidOperationException("JWKS URI not found in discovery document"); throw new InvalidOperationException("JWKS URI not found in discovery document");
} }
// Get Google's signing keys
var client = _httpClientFactory.CreateClient(); var client = _httpClientFactory.CreateClient();
var jwksResponse = await client.GetFromJsonAsync<JsonWebKeySet>(discoveryDocument.JwksUri); var jwksResponse = await client.GetFromJsonAsync<JsonWebKeySet>(discoveryDocument.JwksUri);
if (jwksResponse == null) if (jwksResponse == null)
@ -133,19 +113,15 @@ public class GoogleOidcService(
throw new InvalidOperationException("Failed to retrieve JWKS from Google"); throw new InvalidOperationException("Failed to retrieve JWKS from Google");
} }
// Parse the JWT to get the key ID
var handler = new JwtSecurityTokenHandler(); var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(idToken); var jwtToken = handler.ReadJwtToken(idToken);
var kid = jwtToken.Header.Kid; var kid = jwtToken.Header.Kid;
// Find the matching key
var signingKey = jwksResponse.Keys.FirstOrDefault(k => k.Kid == kid); var signingKey = jwksResponse.Keys.FirstOrDefault(k => k.Kid == kid);
if (signingKey == null) if (signingKey == null)
{ {
throw new SecurityTokenValidationException("Unable to find matching key in Google's JWKS"); throw new SecurityTokenValidationException("Unable to find matching key in Google's JWKS");
} }
// Create validation parameters
var validationParameters = new TokenValidationParameters var validationParameters = new TokenValidationParameters
{ {
ValidateIssuer = true, ValidateIssuer = true,
@ -158,29 +134,4 @@ public class GoogleOidcService(
return ValidateAndExtractIdToken(idToken, validationParameters); return ValidateAndExtractIdToken(idToken, validationParameters);
} }
#region PKCE Support
public string GenerateCodeVerifier()
{
var randomBytes = new byte[32]; // 256 bits
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(randomBytes);
return Convert.ToBase64String(randomBytes)
.Replace('+', '-')
.Replace('/', '_')
.TrimEnd('=');
}
public string GenerateCodeChallenge(string codeVerifier)
{
var challengeBytes = SHA256.HashData(Encoding.UTF8.GetBytes(codeVerifier));
return Convert.ToBase64String(challengeBytes)
.Replace('+', '-')
.Replace('/', '_')
.TrimEnd('=');
}
#endregion
} }