From 6ea54e8127a0a16a0d56718d7a35f21568e3a9c7 Mon Sep 17 00:00:00 2001 From: derp Date: Sat, 25 Apr 2026 12:27:29 -0500 Subject: [PATCH] feat: Add UI for PKCE and JWKS URL upstream OIDC options --- src/interfaces/IdentityProvider.ts | 4 ++ .../settings/IdentityProviderModal.tsx | 43 +++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/interfaces/IdentityProvider.ts b/src/interfaces/IdentityProvider.ts index 75439bc0..ea80a037 100644 --- a/src/interfaces/IdentityProvider.ts +++ b/src/interfaces/IdentityProvider.ts @@ -77,6 +77,8 @@ export interface SSOIdentityProvider { issuer: string; client_id: string; redirect_url?: string; + pkce: boolean; + jwks_url: string; } export interface SSOIdentityProviderRequest { @@ -85,4 +87,6 @@ export interface SSOIdentityProviderRequest { issuer: string; client_id: string; client_secret: string; + pkce: boolean; + jwks_url: string; } diff --git a/src/modules/settings/IdentityProviderModal.tsx b/src/modules/settings/IdentityProviderModal.tsx index 288e1822..ab20c529 100644 --- a/src/modules/settings/IdentityProviderModal.tsx +++ b/src/modules/settings/IdentityProviderModal.tsx @@ -1,5 +1,6 @@ import Button from "@components/Button"; import Code from "@components/Code"; +import FancyToggleSwitch from "@components/FancyToggleSwitch"; import HelpText from "@components/HelpText"; import { Input } from "@components/Input"; import { Label } from "@components/Label"; @@ -30,6 +31,8 @@ import { PlusCircle, SaveIcon, TagIcon, + ShieldCheck, + FileLock2, } from "lucide-react"; import React, { useMemo, useState } from "react"; import { useSWRConfig } from "swr"; @@ -94,6 +97,8 @@ export default function IdentityProviderModal({ const [issuer, setIssuer] = useState(provider?.issuer ?? ""); const [clientId, setClientId] = useState(provider?.client_id ?? ""); const [clientSecret, setClientSecret] = useState(""); + const [pkce, setPkce] = useState(provider?.pkce ?? false); + const [jwksUrl, setJwksUrl] = useState(provider?.jwks_url ?? ""); const requiresIssuer = type !== "google" && type !== "microsoft"; @@ -108,12 +113,13 @@ export default function IdentityProviderModal({ if (trimmedName.length === 0) return true; if (requiresIssuer && trimmedIssuer.length === 0) return true; if (trimmedClientId.length === 0) return true; - // Client secret required for new providers, or when client ID changed during edit - if ((!isEditing || clientIdChanged) && trimmedClientSecret.length === 0) + // Client secret required for new providers, or when client ID changed during edit, + // unless PKCE is enabled which allows public clients without a secret. + if ((!isEditing || clientIdChanged) && trimmedClientSecret.length === 0 && !pkce) return true; return false; - }, [name, issuer, clientId, clientSecret, isEditing, clientIdChanged, requiresIssuer]); + }, [name, issuer, clientId, clientSecret, isEditing, clientIdChanged, requiresIssuer, pkce]); const submit = () => { const payload: SSOIdentityProviderRequest = { @@ -122,6 +128,8 @@ export default function IdentityProviderModal({ issuer: trim(issuer), client_id: trim(clientId), client_secret: trim(clientSecret), + pkce, + jwks_url: trim(jwksUrl), }; if (isEditing) { @@ -259,6 +267,35 @@ export default function IdentityProviderModal({ /> +
+ + + Override the default JSON Web Key Set URL from discovery + + setJwksUrl(e.target.value)} + customPrefix={ + + } + /> +
+ + + + Enable PKCE + + } + helpText={ + "Use Proof Key for Code Exchange (PKCE) for more secure authentication flows" + } + /> +