Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cf0a285

Browse files
committed
fix(auth): make PKCE optional for OAuth sign-in (#5570)
Fixes issue where OAuth sign-in fails with 'Cannot read properties of undefined (reading 'digest')' when accessing Memos over HTTP. The crypto.subtle API is only available in secure contexts (HTTPS or localhost), but PKCE (RFC 7636) is optional per OAuth 2.0 standards. Changes: - Make PKCE generation optional with graceful fallback - Use PKCE when crypto.subtle available (HTTPS/localhost) - Fall back to standard OAuth flow when unavailable (HTTP) - Log warning to console when PKCE unavailable - Only include code_challenge in auth URL when PKCE enabled The backend already supports optional PKCE (empty codeVerifier), so no backend changes needed. This fix aligns frontend behavior with backend. Benefits: - OAuth sign-in works on HTTP deployments (reverse proxy scenarios) - Enhanced security (PKCE) still used when HTTPS available - Backward compatible with OAuth providers that don't support PKCE Fixes #5570
1 parent 7465fbb commit cf0a285

File tree

2 files changed

+37
-9
lines changed

2 files changed

+37
-9
lines changed

web/src/pages/SignIn.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,23 @@ const SignIn = () => {
4949

5050
try {
5151
// Generate and store secure state parameter with CSRF protection
52-
// Also generate PKCE parameters (code_challenge) for enhanced security
52+
// Also generate PKCE parameters (code_challenge) for enhanced security if available
5353
const identityProviderId = extractIdentityProviderIdFromName(identityProvider.name);
5454
const { state, codeChallenge } = await storeOAuthState(identityProviderId);
5555

56-
// Build OAuth authorization URL with secure state and PKCE
56+
// Build OAuth authorization URL with secure state
57+
// Include PKCE if available (requires HTTPS/localhost for crypto.subtle)
5758
// Using S256 (SHA-256) as the code_challenge_method per RFC 7636
58-
const authUrl = `${oauth2Config.authUrl}?client_id=${
59+
let authUrl = `${oauth2Config.authUrl}?client_id=${
5960
oauth2Config.clientId
6061
}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}&response_type=code&scope=${encodeURIComponent(
6162
oauth2Config.scopes.join(" "),
62-
)}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
63+
)}`;
64+
65+
// Add PKCE parameters if available
66+
if (codeChallenge) {
67+
authUrl += `&code_challenge=${codeChallenge}&code_challenge_method=S256`;
68+
}
6369

6470
window.location.href = authUrl;
6571
} catch (error) {

web/src/utils/oauth.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,40 @@ function base64UrlEncode(buffer: Uint8Array): string {
4040
}
4141

4242
// Store OAuth state and PKCE parameters in sessionStorage
43-
// Returns both state and codeChallenge for use in authorization URL
44-
export async function storeOAuthState(identityProviderId: number, returnUrl?: string): Promise<{ state: string; codeChallenge: string }> {
43+
// Returns state and optional codeChallenge for use in authorization URL
44+
// PKCE is optional - if crypto APIs are unavailable (HTTP context), falls back to standard OAuth
45+
export async function storeOAuthState(identityProviderId: number, returnUrl?: string): Promise<{ state: string; codeChallenge?: string }> {
4546
const state = generateSecureState();
46-
const codeVerifier = generateCodeVerifier();
47-
const codeChallenge = await generateCodeChallenge(codeVerifier);
47+
48+
// Try to generate PKCE parameters if crypto.subtle is available (HTTPS/localhost)
49+
// Falls back to standard OAuth flow if unavailable (HTTP context)
50+
let codeVerifier: string | undefined;
51+
let codeChallenge: string | undefined;
52+
53+
try {
54+
// Check if crypto.subtle is available (requires secure context: HTTPS or localhost)
55+
if (typeof crypto !== "undefined" && crypto.subtle) {
56+
codeVerifier = generateCodeVerifier();
57+
codeChallenge = await generateCodeChallenge(codeVerifier);
58+
} else {
59+
console.warn(
60+
"PKCE not available: crypto.subtle requires HTTPS. Falling back to standard OAuth flow without PKCE. " +
61+
"For enhanced security, please access Memos over HTTPS.",
62+
);
63+
}
64+
} catch (error) {
65+
// If PKCE generation fails for any reason, fall back to standard OAuth
66+
console.warn("Failed to generate PKCE parameters, falling back to standard OAuth:", error);
67+
codeVerifier = undefined;
68+
codeChallenge = undefined;
69+
}
4870

4971
const stateData: OAuthState = {
5072
state,
5173
identityProviderId,
5274
timestamp: Date.now(),
5375
returnUrl,
54-
codeVerifier, // Store for later retrieval in callback
76+
codeVerifier, // Store for later retrieval in callback (undefined if PKCE not available)
5577
};
5678

5779
try {

0 commit comments

Comments
 (0)