OpenID Connect 플러그인 리팩토링 (OIDC Implicit Flow)

여러분 안녕하세요,

보안 체계를 개선하고자 하는데, 그 일환으로 가능할 때마다 비밀 자격 증명(secret credentials) 사용을 피해야 합니다.
불행히도 OIDC 플러그인은 /userinfo 엔드포인트에 접근하려면 클라이언트 비밀 자격 증명이 필요했는데, 이 설정을 활성화한 채로 포럼을 구성할 수 없었습니다.

다행히도 OIDC 명세는 실제로 비밀 토큰 사용 없이도 동작하도록 정의되어 있습니다.
id_token 플로우를 따르도록 하면, IdP가 IdP에 다시 연결할 필요 없이 사용자 인증에 필요한 모든 정보를 우리에게 보내줍니다.

리다이렉트가 IdP에서 구성되므로, 베어러 토큰이 잘못된 대상지로 전달될 걱정을 하지 않아도 되어 안전합니다.

OIDC 플러그인이 id_token 플로우를 지원하도록 패치를 작성하여 다음 위치에 풀 리퀘스트를 제출했습니다:

PR은 아직 단위 테스트가 필요하여 완벽하지는 않지만, 거의 완료된 상태이며 Azure AD(Entra ID)에서 정상적으로 작동하는지 확인했습니다.

참고로, OIDC 플러그인의 문서 주소는 다음과 같습니다:

여기서 말씀하시는 내용은 서버 간 통신 없이 작동하는 OpenID Connect의 "Implicit Flow"인 것 같습니다. 따라서 공유 시크릿(shared secret)이 필요하지 않습니다. 대신 모든 정보가 HTTP 리다이렉트를 통해 전송되며, ID 토큰은 Discovery Document의 공개 키를 사용하여 암호학적으로 검증됩니다.

이것은 괜찮은 방법이며, 유효한 기능 요청이라고 생각합니다. 하지만 이것은 현재 우리가 사용하는 플러그인과 매우 다른 시스템입니다. 현재 플러그인은 인가 코드 플로우(authorization code flow)를 사용하며, 이 플로우는 공유 시크릿을 통한 서버 간 통신을 사용하므로 id_token의 암호학적 검증이 필요하지 않습니다. 어떤 면에서는 더 단순하지만, 다른 면에서는 더 복잡합니다.

중요한 점은: 플러그인의 기본 구현을 단순히 변경할 수 없다는 것입니다. 인가 코드 플로우를 사용하고 있는 사이트들은 계속 그 방식을 사용해야 합니다. 기억이 맞다면 많은 인증 제공자(IDP)가 Implicit Flow를 지원하지조차 않습니다.

따라서 앞으로의 방향은 두 가지가 있다고 생각합니다:

  1. OIDC 플러그인에 “implicit flow” 지원을 추가하되, **선택적(opt-in)**으로 만듭니다. openid-connect gem을 Discourse 코어에 의존성으로 추가하는 PR을 승인할 가능성은 낮으므로, 현재 전략 내에서 구현되어야 합니다.

    discourse-lti(학습 도구 통합) 플러그인이 유용한 참고 자료가 될 수 있습니다. LTI 프로토콜은 OIDC implicit flow에 기반하기 때문입니다. id_token 디코딩 로직은 여기에 있습니다.

또는

  1. implicit flow를 완전히 새로운 플러그인으로 구축합니다. 이 경우 구현 방식에 대해 자유롭게 결정할 수 있지만(단, 직접 유지보수도 해야 합니다).

OIDC의 "Implicit Flow"가 CSRF 공격 및 토큰 노출의 표면을 증가시킨다는 점도 주목할 만합니다:

일부 상황에서는 여전히 의미가 있을 수 있다고 생각합니다. 하지만 PKCE(Discourse에서 선택 사항)를 사용한 Authorization Code 플로우(Discourse의 기본값)가 OIDC를 사용하는 가장 권장되는 방법인 것으로 보입니다.

따라서 다음 진술을 재고해 볼 가치가 있을 수 있습니다:

OIDC implicit flow로 전환하여 모든 사용자 정보를 클라이언트를 통해 전송하는 것이 정말로 보안 포지션의 개선인가요? :thinking:

자세한 답변 감사합니다!

특정 상황에서는 여전히 의미가 있을 수 있다고 생각합니다. 하지만 Authorization Code 플로우(Discourse의 기본값)에 PKCE(Discourse에서는 선택 사항)를 사용하는 것이 OIDC를 사용할 때 가장 권장되는 방식인 것 같습니다.

매우 흥미롭습니다. PKCE에 대해 이제야 제대로 이해하게 되었습니다. Auth0 문서를 읽어본 결과, (특정 구성에서) 이 플로우는 임플리시트 플로우의 장점(클라이언트 시크릿 불필요)은 가져가면서 단점은 전혀 없는 것 같습니다.

이것이 대안으로 가능할까요? /token 엔드포인트에서 클라이언트 시크릿 파라미터를 제거하는 것만으로 간단해 보입니다.

결국 제 주요 관심사는 클라이언트 시크릿 요구 사항을 없애는 것입니다 :slight_smile:

다음 구성을 사용하면 작동합니다:

"DISCOURSE_OPENID_CONNECT_ENABLED": "true",
"DISCOURSE_OPENID_CONNECT_DISCOVERY_DOCUMENT": "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
"DISCOURSE_OPENID_CONNECT_CLIENT_ID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"DISCOURSE_OPENID_CONNECT_CLIENT_SECRET": "",
"DISCOURSE_OPENID_CONNECT_VERBOSE_LOGGING": "true",
"DISCOURSE_OPENID_CONNECT_USE_PKCE": "true"

그리고 코드에 다음과 같은 변경 사항이 필요합니다:

diff --git a/plugins/discourse-openid-connect/lib/omniauth_open_id_connect.rb b/plugins/discourse-openid-connect/lib/omniauth_open_id_connect.rb
index 410a88f46dc..e74ee360aae 100644
--- a/plugins/discourse-openid-connect/lib/omniauth_open_id_connect.rb
+++ b/plugins/discourse-openid-connect/lib/omniauth_open_id_connect.rb
@@ -73,6 +73,11 @@ module OmniAuth
              )
           options[:client_options][:auth_scheme] = :request_body
         end
+
+        # PKCE를 사용 중이고 _그리고_ client_secret가 없으면 request_body auth_scheme을 사용합니다.
+        if options[:pkce] && options[:client_secret].empty?
+          options[:client_options][:auth_scheme] = :request_body
+        end
       end
 
       def request_phase

리다이렉트 URI는 Azure 제어판에서 다음과 같이 구성됩니다:

감사합니다, @justinm. PR을 병합하고 문서에 아래 섹션을 추가했습니다: