🐛 Remove PKCE on Google OIDC
This commit is contained in:
parent
ada84f85e9
commit
5c02c63f70
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user