diff --git a/package.json b/package.json
index 143cd234a..fa563280e 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
     "inferno-router": "^8.2.3",
     "inferno-server": "^8.2.3",
     "jwt-decode": "^4.0.0",
-    "lemmy-js-client": "0.20.0-alpha.17",
+    "lemmy-js-client": "0.20.0-pkce.1",
     "lodash.isequal": "^4.5.0",
     "markdown-it": "^14.1.0",
     "markdown-it-bidi": "^0.2.0",
diff --git a/src/shared/components/common/modal/create-or-edit-oauth-provider-modal.tsx b/src/shared/components/common/modal/create-or-edit-oauth-provider-modal.tsx
index a0112f27c..e7a63e44f 100644
--- a/src/shared/components/common/modal/create-or-edit-oauth-provider-modal.tsx
+++ b/src/shared/components/common/modal/create-or-edit-oauth-provider-modal.tsx
@@ -51,6 +51,7 @@ interface ProviderTextFieldProps extends ProviderFieldProps {
 
 type ProviderBooleanProperties =
   | "enabled"
+  | "use_pkce"
   | "account_linking_enabled"
   | "auto_verify_email";
 
@@ -337,6 +338,18 @@ export default class CreateOrEditOAuthProviderModal extends Component<
                         handleBooleanPropertyChange,
                       )}
                     />
+                    <ProviderCheckboxField
+                      id="use-pkce"
+                      i18nKey="use_pkce"
+                      checked={provider?.use_pkce}
+                      onInput={linkEvent(
+                        {
+                          modal: this,
+                          property: "use_pkce",
+                        },
+                        handleBooleanPropertyChange,
+                      )}
+                    />
                     <ProviderCheckboxField
                       id="oauth-enabled"
                       i18nKey="oauth_enabled"
diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx
index 01f3d781f..84fdbddff 100644
--- a/src/shared/components/home/login.tsx
+++ b/src/shared/components/home/login.tsx
@@ -25,6 +25,10 @@ import { UnreadCounterService } from "../../services";
 import { RouteData } from "../../interfaces";
 import { IRoutePropsWithFetch } from "../../routes";
 import { simpleScrollMixin } from "../mixins/scroll-mixin";
+import {
+  generateCodeVerifier,
+  createCodeChallenge,
+} from "@utils/helpers/oauth";
 
 interface LoginProps {
   prev?: string;
@@ -126,16 +130,32 @@ export async function handleUseOAuthProvider(params: {
   const redirectUri = `${window.location.origin}/oauth/callback`;
 
   const state = crypto.randomUUID();
+
+  let codeVerifier: string | undefined;
+  if (params.oauth_provider.use_pkce) {
+    codeVerifier = generateCodeVerifier();
+  }
+  let codeChallenge: string | undefined;
+  if (codeVerifier) {
+    codeChallenge = await createCodeChallenge(codeVerifier);
+  }
+
+  const queryPairs = [
+    `client_id=${encodeURIComponent(params.oauth_provider.client_id)}`,
+    `response_type=code`,
+    `scope=${encodeURIComponent(params.oauth_provider.scopes)}`,
+    `redirect_uri=${encodeURIComponent(redirectUri)}`,
+    `state=${state}`,
+    ...(codeChallenge
+      ? [
+          `code_challenge=${encodeURIComponent(codeChallenge)}`,
+          "code_challenge_method=S256",
+        ]
+      : []),
+  ];
+
   const requestUri =
-    params.oauth_provider.authorization_endpoint +
-    "?" +
-    [
-      `client_id=${encodeURIComponent(params.oauth_provider.client_id)}`,
-      `response_type=code`,
-      `scope=${encodeURIComponent(params.oauth_provider.scopes)}`,
-      `redirect_uri=${encodeURIComponent(redirectUri)}`,
-      `state=${state}`,
-    ].join("&");
+    params.oauth_provider.authorization_endpoint + "?" + queryPairs.join("&");
 
   // store state in local storage
   localStorage.setItem(
@@ -149,6 +169,7 @@ export async function handleUseOAuthProvider(params: {
       answer: params.answer,
       show_nsfw: params.show_nsfw,
       expires_at: Date.now() + 5 * 60_000,
+      ...(codeVerifier ? { pkce_code_verifier: codeVerifier } : {}),
     }),
   );
 
diff --git a/src/shared/components/home/oauth/oauth-callback.tsx b/src/shared/components/home/oauth/oauth-callback.tsx
index 8d8ea3db8..2ebdf1b02 100644
--- a/src/shared/components/home/oauth/oauth-callback.tsx
+++ b/src/shared/components/home/oauth/oauth-callback.tsx
@@ -79,6 +79,9 @@ export class OAuthCallback extends Component<OAuthCallbackRouteProps, State> {
         show_nsfw: local_oauth_state.show_nsfw,
         username: local_oauth_state.username,
         answer: local_oauth_state.answer,
+        ...(local_oauth_state?.pkce_code_verifier && {
+          pkce_code_verifier: local_oauth_state.pkce_code_verifier,
+        }),
       });
 
       switch (loginRes.state) {
diff --git a/src/shared/components/home/oauth/oauth-provider-list-item.tsx b/src/shared/components/home/oauth/oauth-provider-list-item.tsx
index e87ae3f1f..b538e5a2c 100644
--- a/src/shared/components/home/oauth/oauth-provider-list-item.tsx
+++ b/src/shared/components/home/oauth/oauth-provider-list-item.tsx
@@ -86,6 +86,10 @@ export default function OAuthProviderListItem({
               i18nKey="oauth_account_linking_enabled"
               data={boolToYesNo(provider.account_linking_enabled)}
             />
+            <TextInfoField
+              i18nKey="use_pkce"
+              data={boolToYesNo(provider.use_pkce)}
+            />
             <TextInfoField
               i18nKey="oauth_enabled"
               data={boolToYesNo(provider.enabled)}
diff --git a/src/shared/components/home/oauth/oauth-providers-tab.tsx b/src/shared/components/home/oauth/oauth-providers-tab.tsx
index e6efa5378..4ffbbacad 100644
--- a/src/shared/components/home/oauth/oauth-providers-tab.tsx
+++ b/src/shared/components/home/oauth/oauth-providers-tab.tsx
@@ -36,6 +36,7 @@ const PRESET_OAUTH_PROVIDERS: ProviderToEdit[] = [
     scopes: "openid email",
     auto_verify_email: true,
     account_linking_enabled: true,
+    use_pkce: true,
     enabled: true,
   },
   // additional preset providers can be added here
diff --git a/src/shared/utils/helpers/oauth.ts b/src/shared/utils/helpers/oauth.ts
new file mode 100644
index 000000000..31e24f5ad
--- /dev/null
+++ b/src/shared/utils/helpers/oauth.ts
@@ -0,0 +1,19 @@
+export function base64URLEncode(buffer: Uint8Array | ArrayBuffer) {
+  return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)))
+    .replace(/\//g, "_")
+    .replace(/\+/g, "-")
+    .replace(/=/g, "");
+}
+
+export function generateCodeVerifier(length: number = 64) {
+  const array = new Uint8Array(length);
+  window.crypto.getRandomValues(array);
+  return base64URLEncode(array);
+}
+
+export async function createCodeChallenge(codeVerifier: string) {
+  const encoder = new TextEncoder();
+  const data = encoder.encode(codeVerifier);
+  const digest = await window.crypto.subtle.digest("SHA-256", data);
+  return base64URLEncode(digest);
+}