feat(sso): PKCE, Nonce 검증, Keycloak end_session 로그아웃 보안 강화#12381
Closed
YeonghyeonKO wants to merge 23 commits intolangflow-ai:mainfrom
Closed
feat(sso): PKCE, Nonce 검증, Keycloak end_session 로그아웃 보안 강화#12381YeonghyeonKO wants to merge 23 commits intolangflow-ai:mainfrom
YeonghyeonKO wants to merge 23 commits intolangflow-ai:mainfrom
Conversation
Implements Keycloak OIDC authentication via the Langflow plugin system.
Users are authenticated with company Keycloak credentials; members of
the same Keycloak group share a single Langflow account (group → account
mapping is managed at runtime via a DB table and admin API).
Key components:
- langflow-keycloak-sso: plugin package registered via langflow.plugins
entry-point; adds /api/v1/keycloak/{config,login,callback,mappings}
- OIDC flow: authorization code + PKCE-less with JWKS signature verify
- Shared account auto-provisioning on first login
- Login page: "Login with Keycloak" button shown when KEYCLOAK_ENABLED=true
- mock_oidc_server.py: standalone FastAPI OIDC mock for local development
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For local Keycloak SSO development/testing only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Authorization is fully delegated to Keycloak (client-level access control). Any user who passes Keycloak is logged into one shared Langflow account configured via KEYCLOAK_SHARED_USERNAME — no group mapping DB needed. - Remove KeycloakGroupMapping DB table and admin API endpoints - Remove models.py (no DB schema needed) - Replace group→account mapping with KEYCLOAK_SHARED_USERNAME env var - Update mock OIDC server with 10 employees (사번 EMP001~EMP010): - EMP001, EMP002: 모든 프로젝트 접근 가능 - EMP003~EMP005: project-a 전용 - EMP006~EMP008: project-b 전용 - EMP009, EMP010: 접근 권한 없음 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LoginPage: remove username/password form; show only "SSO 로그인" button - AccountMenu: override logout to call GET /api/v1/keycloak/logout (bypasses IS_AUTO_LOGIN bug that skipped cookie deletion in JS logout) - router.py: add GET /api/v1/keycloak/logout endpoint that deletes all session cookies server-side and redirects to /login - scripts/mock_oidc_server.py: rewrite with group-based client access control (langflow-admins / project-a-members / project-b-members, EMP001-EMP010), uv inline script metadata for zero-setup dependency mgmt - scripts/start_dev.sh: start project-a (:7860) and project-b (:7861) on subdomains (project-a.localhost / project-b.localhost) to isolate cookies between instances Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- docker/keycloak-sso.Dockerfile: multi-stage build (builder + runtime), installs langflow-keycloak-sso plugin via uv pip after workspace sync, builds modified frontend (SSO-only login page) in builder stage - docker/keycloak-sso.docker-compose.yml: local multi-instance test setup (project-a :7860, project-b :7861, mock-oidc :9000) Build: docker build -f docker/keycloak-sso.Dockerfile -t langflow-keycloak-sso . Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deleted 62 component directories and removed them from __init__.py (_dynamic_imports, __all__, TYPE_CHECKING imports): agentql, aiml, altk, amazon, anthropic, apify, arxiv, assemblyai, azure, baidu, bing, cassandra, chroma, cleanlab, crewai, datastax, deepseek, duckduckgo, exa, firecrawl, glean, google, groq, homeassistant, huggingface, ibm, icosacomputing, jigsawstack, langwatch, lmstudio, maritalk, mem0, mistral, mongodb, needle, Notion, notdiamond, novita, nvidia, olivya, ollama, perplexity, pinecone, qdrant, sambanova, scrapegraph, searchapi, serpapi, supabase, twelvelabs, unstructured, upstash, vectara, vertexai, vlmrun, weaviate, wikipedia, wolframalpha, xai, yahoosearch, youtube, zep Remaining: FAISS, chains, clickhouse, cloudflare, cohere, composio, confluence, couchbase, cuga, custom_component, data, deactivated, docling, documentloaders, elastic, embeddings, git, helpers, input_output, langchain_utilities, link_extractors, litellm, logic, milvus, models_and_agents, openai, openrouter, output_parsers, pgvector, processing, prototypes, redis, tavily, textsplitters, toolkits, tools, vectorstores Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…for Node.js - Move langflow-keycloak-sso install to after final uv sync to prevent --frozen cleanup from removing the plugin (it's not in the lockfile) - Replace dynamic Node.js tarball detection (grep -oP Perl regex) with nodesource setup_22.x in runtime stage — fixes ARM/amd64 compatibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When Langflow runs in Docker, KEYCLOAK_SERVER_URL points to the internal service name (e.g. http://mock-oidc:9000) which the browser cannot resolve. KEYCLOAK_EXTERNAL_SERVER_URL overrides only the authorization_endpoint (browser redirect) while token exchange and JWKS still use SERVER_URL. Updated docker-compose to set EXTERNAL_SERVER_URL=http://localhost:9000 so the browser is redirected to the host-accessible mock OIDC server. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: Keycloak SSO plugin — instance-scoped shared account login
- textAreaComponent: add localValue state and composition guards to prevent setSelectionRange and parent state updates during IME composition, fixing Korean consonant/vowel separation in Parser Template and LLM System Message inputs - CustomInputPopover: same localValue + composition pattern for general text inputs - accordionPromptComponent: skip innerHTML update during composition to prevent breaking Korean input in contentEditable prompt fields - LoginPage: change SSO login button text to 'SK하이닉스 SSO 로그인' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed packages for the 62 deleted lfx components from src/backend/base/pyproject.toml: Direct deps removed (not imported anywhere in remaining code): - firecrawl-py (firecrawl component deleted) - assemblyai (assemblyai component deleted) - onnxruntime (no remaining usage) Removed from [complete] extras (corresponding components deleted): - Vector stores: cassandra, mongodb, mongodb-vectors, qdrant, weaviate, upstash, pinecone, astradb, astra, astrapy, ragstack - LLM providers: anthropic, groq, mistral, sambanova, nvidia - AWS: aws, aioboto3 - Other: altk, apify, assemblyai, cleanlab, dspy, duckduckgo, huggingface, jigsawstack, langchain-unstructured, mem0, metal, metaphor, needle, pydantic-ai, pytube, qianfan, ragstack, scrapegraph, serpapi, smolagents, supabase, twelvelabs, vlmrun, weaviate, wikipedia, wolframalpha, youtube, zep, ag2 Packages kept: ibm-watsonx-ai, langchain-ibm (used by models_and_agents), langchain-chroma (used by vectorstores), scipy/elevenlabs (voice features), spider-client (langchain_utilities), clickhouse-connect (clickhouse), langchain-ollama (files_and_knowledge/retrieval), google packages (files_and_knowledge/retrieval + tools/google_search_api), langchain-huggingface (files_and_knowledge/retrieval), langwatch (tracing service), opensearch (elastic components) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…re during IME composition - accordionPromptComponent: move handleOnNewValue inside !isComposingRef.current guard so partial IME states (e.g. individual Hangul jamo) are never pushed upstream; merge with the existing innerHTML guard into a single block - popover: add !isComposing guard and isComposing to deps of cursor restore useEffect so setSelectionRange is never called while IME is active - textAreaComponent: add isComposing to deps of cursor restore useEffect (guard was already present; adds exhaustive-deps compliance and ensures the effect re-runs when composition ends) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ci.yml: remove ANTHROPIC_API_KEY from env — Anthropic components are not used in hynix fork, so key should not be injected; tests that check ANTHROPIC_API_KEY will now correctly skip instead of failing with a 400 credit-balance error - typescript_test.yml: change shard formula from 1/5 (max 70) to 1/15 (max 20) — reduces ~57 parallel shards to ~19, cutting CI wall-clock time significantly for a fork that runs fewer integration flows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: resolve Korean IME input issue and update SSO login button text
This reverts commit d04acb6.
Feat/keycloak sso
- Add ignoreTitleCase prop to SSO login button to prevent toTitleCase() from converting "SK하이닉스 SSO 로그인" → "Sk하이닉스 Sso 로그인" - Update KEYCLOAK_BUTTON_TEXT default in Dockerfile to "SK하이닉스 SSO 로그인" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Read KEYCLOAK_BUTTON_TEXT env var via /api/v1/keycloak/config instead of hardcoding, so each instance can display its own label without a frontend rebuild. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
langflow-base was overwriting langflow's frontend because both packages install into langflow/frontend/. Fixed by copying the npm build output directly into src/backend/base/langflow/frontend (after clearing it), so langflow-base's wheel includes the freshly built frontend. Also added rm -rf to ensure a clean copy (cp -r into existing dir creates a subdirectory instead of replacing). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
레퍼런스 구현(https://github.com/Wonki4/llm-ops) 분석을 통해 식별한 세 가지 보안 취약점을 Keycloak SSO 플러그인에 적용합니다. - PKCE S256: /login에서 code_verifier/code_challenge 생성, state JWT에 code_verifier 저장, /callback에서 token exchange 시 code_verifier 전달 - Nonce 검증: /login에서 nonce 생성 후 auth URL과 state JWT에 포함, /callback에서 id_token nonce claim 비교로 재전송 공격 방지 - Keycloak end_session 로그아웃: 로그인 시 id_token을 kc_id_token_lf 쿠키에 저장, /logout에서 id_token_hint로 Keycloak end_session_endpoint 호출하여 SSO 세션 완전 종료 - settings.py에 end_session_endpoint 프로퍼티 및 LOGOUT_REDIRECT_URI 설정 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
|
Important Review skippedToo many files! This PR contains 232 files, which is 82 over the limit of 150. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (232)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Author
|
Wrong repository - this PR was unintentionally opened against the upstream repo. The intended PR is at YeonghyeonKO#5. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
개요
레퍼런스 구현 Wonki4/llm-ops를 분석하여 현재 Keycloak SSO 플러그인에 누락된 세 가지 보안 기능을 식별하고 적용합니다.
식별된 보안 취약점
/login으로 단순 리다이렉트변경 내용
settings.pyend_session_endpoint프로퍼티 추가:{SERVER_URL}/realms/{REALM}/protocol/openid-connect/logoutLOGOUT_REDIRECT_URI: str = ""설정 추가 (Keycloak 로그아웃 후 리다이렉트 대상, 미설정 시REDIRECT_URIorigin +/login자동 구성)keycloak_client.pyexchange_code메서드에code_verifier: str | None = None파라미터 추가code_verifier가 있을 때 token endpoint POST 데이터에 포함router.py/login에서_generate_pkce()로code_verifier/code_challenge생성, state JWT에code_verifier저장, auth URL에code_challenge·code_challenge_method=S256추가/login에서 nonce 생성, state JWT와 auth URL 파라미터에 포함./callback에서 id_token의nonceclaim과 비교하여 불일치 시401반환/callback에서 Keycloakid_token을kc_id_token_lfhttpOnly 쿠키로 저장/logout에request: Request파라미터 추가,kc_id_token_lf쿠키 읽어id_token_hint로 Keycloakend_session_endpoint호출.kc_id_token_lf쿠키도 삭제 목록에 포함의도적으로 적용하지 않은 것
레퍼런스 구현과 비교해 다음 항목은 아키텍처 차이로 인해 의도적으로 적용하지 않습니다:
KEYCLOAK_SHARED_USERNAME) 방식을 유지합니다. 인가(Authorization)는 Keycloak에 완전히 위임하는 설계입니다.테스트 체크리스트
401 Nonce mismatch응답 확인KEYCLOAK_LOGOUT_REDIRECT_URI미설정 시REDIRECT_URIorigin +/login으로 자동 구성 확인/login으로 리다이렉트 확인🤖 Generated with Claude Code