From 3a057ee860d317a179347107802b0fc3f93d40cb Mon Sep 17 00:00:00 2001 From: kyy Date: Mon, 30 Mar 2026 13:03:04 +0900 Subject: [PATCH] =?UTF-8?q?Trusted=20RP=20=EC=84=A4=EC=A0=95=20UX=20?= =?UTF-8?q?=EB=B0=8F=20=EC=95=88=EB=82=B4=20=EB=AC=B8=EA=B5=AC=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/clients/ClientGeneralPage.tsx | 112 +++++++----------- devfront/src/locales/en.toml | 7 +- devfront/src/locales/ko.toml | 12 +- devfront/src/locales/template.toml | 6 +- locales/en.toml | 2 + locales/ko.toml | 2 + locales/template.toml | 2 + 7 files changed, 71 insertions(+), 72 deletions(-) diff --git a/devfront/src/features/clients/ClientGeneralPage.tsx b/devfront/src/features/clients/ClientGeneralPage.tsx index a5c60c16..85762cea 100644 --- a/devfront/src/features/clients/ClientGeneralPage.tsx +++ b/devfront/src/features/clients/ClientGeneralPage.tsx @@ -48,7 +48,7 @@ interface ScopeItem { mandatory: boolean; } -type SecurityProfile = "private" | "pkce" | "private_key_jwt"; +type SecurityProfile = "private" | "pkce"; function readMetadataString( metadata: Record, @@ -101,10 +101,11 @@ function ClientGeneralPage() { const [tokenEndpointAuthMethod, setTokenEndpointAuthMethod] = useState< "none" | "client_secret_basic" | "private_key_jwt" >("client_secret_basic"); - const [jwksSource, setJwksSource] = useState<"uri" | "inline">("uri"); + const [jwksSource, setJwksSource] = useState<"uri" | "inline">("inline"); const [jwksUri, setJwksUri] = useState(""); const [jwksText, setJwksText] = useState(""); const [requestObjectSigningAlg, setRequestObjectSigningAlg] = useState("RS256"); + const [headlessLoginEnabled, setHeadlessLoginEnabled] = useState(false); const [scopes, setScopes] = useState(() => [ { @@ -150,6 +151,8 @@ function ClientGeneralPage() { if (typeof metadata.description === "string") setDescription(metadata.description); if (typeof metadata.logo_url === "string") setLogoUrl(metadata.logo_url); + setHeadlessLoginEnabled(!!metadata.headless_login_enabled); + // Fallbacks from metadata if top-level fields are empty if (!client.tokenEndpointAuthMethod) { const metaAuth = readMetadataString(metadata, "token_endpoint_auth_method"); @@ -188,38 +191,25 @@ function ClientGeneralPage() { } }, [data]); - const securityProfile: SecurityProfile = - tokenEndpointAuthMethod === "private_key_jwt" - ? "private_key_jwt" - : clientType === "pkce" - ? "pkce" - : "private"; - - const headlessLoginEnabled = securityProfile === "private_key_jwt"; + const securityProfile: SecurityProfile = clientType === "pkce" ? "pkce" : "private"; const handleSecurityProfileChange = (profile: SecurityProfile) => { + setClientType(profile); if (profile === "pkce") { - setClientType("pkce"); - setTokenEndpointAuthMethod("none"); - setJwksUri(""); - setJwksText(""); - setRequestObjectSigningAlg(""); - return; + setTokenEndpointAuthMethod(headlessLoginEnabled ? "private_key_jwt" : "none"); + } else { + setTokenEndpointAuthMethod("client_secret_basic"); } + }; - setClientType("private"); - if (profile === "private_key_jwt") { - setTokenEndpointAuthMethod("private_key_jwt"); - if (requestObjectSigningAlg.trim() === "") { + const handleHeadlessToggle = (enabled: boolean) => { + setHeadlessLoginEnabled(enabled); + if (clientType === "pkce") { + setTokenEndpointAuthMethod(enabled ? "private_key_jwt" : "none"); + if (enabled && requestObjectSigningAlg.trim() === "") { setRequestObjectSigningAlg("RS256"); } - return; } - - setTokenEndpointAuthMethod("client_secret_basic"); - setJwksUri(""); - setJwksText(""); - setRequestObjectSigningAlg(""); }; const addScope = () => { @@ -794,7 +784,7 @@ function ClientGeneralPage() { -
+
-
)} - > - handleSecurityProfileChange("private_key_jwt")} - /> - - - {t("ui.dev.clients.general.security.trusted", "Trusted RP")} - - - {t( - "msg.dev.clients.general.security.trusted_help", - "private_key_jwt와 공개키 등록을 사용해 trusted RP로 운영합니다. Headless Login은 이 프로필에서만 사용할 수 있습니다.", - )} - - - {securityProfile === "private_key_jwt" ? "✓" : ""} -
{/* 4. Public Key Registration (Trusted RP) */} - {securityProfile === "private_key_jwt" && ( + {clientType === "pkce" && headlessLoginEnabled && (
@@ -902,9 +883,6 @@ function ClientGeneralPage() { "ui.dev.clients.general.public_key.title", "Public Key Registration", )} - - Trusted RP - {t( @@ -928,7 +906,7 @@ function ClientGeneralPage() {

{t( "msg.dev.clients.general.public_key.headless_help", - "RP 자체 로그인 UI를 사용하고, 실제 인증 처리는 Baron Backend API를 통해 백그라운드에서 수행합니다.", + "애플리케이션 고유의 디자인으로 로그인 화면을 구성할 수 있습니다. 실제 아이디/비밀번호 확인 및 보안 검증 로직은 Baron API를 통해 백그라운드에서 처리됩니다.", )}

@@ -985,21 +963,21 @@ function ClientGeneralPage() { setJwksSource("uri")} + checked={jwksSource === "inline"} + onChange={() => setJwksSource("inline")} className="accent-primary" /> - JWKS URI (권장) + Inline Public Key @@ -1117,4 +1095,4 @@ function ClientGeneralPage() { ); } -export default ClientGeneralPage; \ No newline at end of file +export default ClientGeneralPage; diff --git a/devfront/src/locales/en.toml b/devfront/src/locales/en.toml index cf693b69..74fa2cf2 100644 --- a/devfront/src/locales/en.toml +++ b/devfront/src/locales/en.toml @@ -401,7 +401,8 @@ guide_intro = "A JWKS URI is not created by Baron. It is the URL where the RP ba guide_step_1 = "Generate a key pair on the RP server and keep the private key only in the RP backend." guide_step_2 = "Expose the public key from the RP backend through a JWKS (JSON Web Key Set) endpoint." guide_step_3 = "Enter a URL such as https://rp.example.com/.well-known/jwks.json in DevFront." -headless_help = "Trusted RPs can keep their own login UI while Baron continues to handle authentication and OIDC progression." +headless_help = "You can design your own login UI within the application. While the UI is yours, the actual identity verification and security checks are handled in the background via Baron's API." +jwks_inline_help = "Prefer the SSH-RSA public key format first. If you paste an 'ssh-rsa AAA...' key, Baron converts it to OIDC-standard JWKS (JSON) before saving." jwks_uri_help = "Enter the public key endpoint URL exposed by the RP backend. Example: https://rp.example.com/.well-known/jwks.json" request_object_alg_help = "Specify the JAR (Request Object) signing algorithm used for headless login." source_help = "Register the JWKS URI served by the RP so Baron can verify the public key." @@ -1393,6 +1394,8 @@ private = "Server Side App" pkce = "PKCE" trusted = "Trusted RP" title = "Security Settings" +trusted_rp_enable = "Trusted RP (Custom Login UI)" +trusted_rp_enable_help = "Enable this if you want to implement your own login screen within the app instead of using the Baron SSO login page." [ui.dev.clients.general.public_key] auth_method = "Token Endpoint Auth Method" @@ -1403,6 +1406,8 @@ guide_toggle = "JWKS URI Setup Guide" headless_disabled = "Headless Disabled" headless_enabled = "Headless Enabled" headless_toggle = "Headless Login" +jwks_inline = "SSH-RSA or JWKS Public Key" +jwks_inline_placeholder = "Paste an 'ssh-rsa AAA...' public key first. JWKS (JSON) is also accepted if needed." jwks_uri = "JWKS URI" jwks_uri_placeholder = "https://rp.example.com/.well-known/jwks.json" request_object_alg = "Request Object Signing Algorithm" diff --git a/devfront/src/locales/ko.toml b/devfront/src/locales/ko.toml index 45f77f6f..55d174e6 100644 --- a/devfront/src/locales/ko.toml +++ b/devfront/src/locales/ko.toml @@ -401,10 +401,11 @@ guide_intro = "JWKS URI는 Baron이 만드는 값이 아니라 RP backend가 공 guide_step_1 = "RP 서버에서 key pair를 생성하고 private key는 RP backend에만 보관합니다." guide_step_2 = "RP backend가 public key를 JWKS(JSON Web Key Set) 형태로 제공하는 endpoint를 준비합니다." guide_step_3 = "예: https://rp.example.com/.well-known/jwks.json 같은 URL을 DevFront에 입력합니다." -headless_help = "Trusted RP는 RP 자체 로그인 UI를 사용할 수 있지만, bootstrap 검증, 사용자 인증 처리, Hydra 연계, 최종 redirect 생성은 Baron backend가 담당합니다." +headless_help = "애플리케이션 고유의 디자인으로 로그인 화면을 구성할 수 있습니다. 실제 아이디/비밀번호 확인 및 보안 검증 로직은 Baron API를 통해 백그라운드에서 처리됩니다." +jwks_inline_help = "SSH-RSA 공개키 형식을 우선 권장합니다. 'ssh-rsa AAA...' 형식으로 입력하면 Baron이 OIDC 표준인 JWKS(JSON)로 자동 변환하여 저장합니다." jwks_uri_help = "RP backend가 제공하는 공개키 endpoint URL을 입력하세요. 예: https://rp.example.com/.well-known/jwks.json" request_object_alg_help = "Headless Login을 사용할 때 JAR(Request Object) 서명 알고리즘을 명시합니다." -source_help = "운영 환경에서는 RP가 서빙하는 JWKS URI를 등록해 공개키를 검증합니다." +source_help = "애플리케이션의 공개키(SSH-RSA)를 직접 등록하거나, 운영 환경이라면 JWKS URI를 통해 자동으로 검증할 수 있습니다." subtitle = "Trusted RP 판정에 필요한 공개키와 headless login 관련 설정을 관리합니다." [msg.dev.clients.general.public_key.validation] @@ -412,6 +413,7 @@ headless_requires_alg = "Headless Login을 사용하려면 Request Object Signin headless_requires_private_key_jwt = "Headless Login을 사용하려면 token endpoint auth method가 private_key_jwt여야 합니다." headless_requires_public_key = "Headless Login을 사용하려면 JWKS URI가 필요합니다." invalid_jwks_uri = "JWKS URI 형식이 올바르지 않습니다." +missing_jwks_inline = "공개키(SSH-RSA 또는 JWKS)를 입력해야 합니다." private_key_jwt_requires_public_key = "서명 키 기반 인증을 사용하려면 JWKS URI가 필요합니다." [msg.dev.clients.help] @@ -1390,8 +1392,10 @@ delete = "삭제" [ui.dev.clients.general.security] private = "Server side App" pkce = "PKCE" -trusted = "Trusted RP" title = "보안 설정" +trusted_rp_enable = "Trusted RP (자체 로그인 UI 사용)" +trusted_rp_enable_help = "Baron SSO 로그인 창을 거치지 않고 애플리케이션 내의 자체 로그인 화면을 직접 구현하고 싶은 경우 활성화합니다." + [ui.dev.clients.general.public_key] auth_method = "Token Endpoint Auth Method" @@ -1402,6 +1406,8 @@ guide_toggle = "JWKS URI 준비 가이드" headless_disabled = "Headless Disabled" headless_enabled = "Headless Enabled" headless_toggle = "Headless Login" +jwks_inline = "SSH-RSA 또는 JWKS 공개키" +jwks_inline_placeholder = "'ssh-rsa AAA...' 형식의 공개키를 먼저 붙여넣으세요. 필요하면 JWKS (JSON)도 입력할 수 있습니다." jwks_uri = "JWKS URI" jwks_uri_placeholder = "https://rp.example.com/.well-known/jwks.json" request_object_alg = "Request Object Signing Algorithm" diff --git a/devfront/src/locales/template.toml b/devfront/src/locales/template.toml index 2fbbd1de..69cea372 100644 --- a/devfront/src/locales/template.toml +++ b/devfront/src/locales/template.toml @@ -402,6 +402,7 @@ guide_step_1 = "" guide_step_2 = "" guide_step_3 = "" headless_help = "" +jwks_inline_help = "" jwks_uri_help = "" request_object_alg_help = "" source_help = "" @@ -1390,8 +1391,9 @@ delete = "" [ui.dev.clients.general.security] private = "" pkce = "" -trusted = "" title = "" +trusted_rp_enable = "" +trusted_rp_enable_help = "" [ui.dev.clients.general.public_key] auth_method = "" @@ -1402,6 +1404,8 @@ guide_toggle = "" headless_disabled = "" headless_enabled = "" headless_toggle = "" +jwks_inline = "" +jwks_inline_placeholder = "" jwks_uri = "" jwks_uri_placeholder = "" request_object_alg = "" diff --git a/locales/en.toml b/locales/en.toml index f66fe9d6..7075e54b 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -1459,6 +1459,8 @@ delete = "Delete" private = "Server Side App" pkce = "PKCE" title = "Security Settings" +trusted_rp_enable = "Trusted RP (Custom Login UI)" +trusted_rp_enable_help = "Enable this if you want to implement your own login screen within the app instead of using the Baron SSO login page." [ui.dev.clients.help] docs_body = "Includes PKCE, client_secret_basic, redirect URI validation tips." diff --git a/locales/ko.toml b/locales/ko.toml index f3e12edd..6c599737 100644 --- a/locales/ko.toml +++ b/locales/ko.toml @@ -1712,6 +1712,8 @@ title = "스코프" private = "Server side App" pkce = "PKCE" title = "보안 설정" +trusted_rp_enable = "Trusted RP (자체 로그인 UI 사용)" +trusted_rp_enable_help = "Baron SSO 로그인 창을 거치지 않고 애플리케이션 내의 자체 로그인 화면을 직접 구현하고 싶은 경우 활성화합니다." [ui.dev.dashboard.ops.card] consent_revoked = "Consent 회수 건수" diff --git a/locales/template.toml b/locales/template.toml index de802eb1..1028b5d5 100644 --- a/locales/template.toml +++ b/locales/template.toml @@ -1706,6 +1706,8 @@ title = "" private = "" pkce = "" title = "" +trusted_rp_enable = "" +trusted_rp_enable_help = "" [ui.dev.dashboard.ops.card] consent_revoked = ""