Discourseをアイデンティティプロバイダとして使用する(SSO、DiscourseConnect)

こんにちは :waving_hand: 私は、SimpleSAMLphp のインストール内で Discourse を SSO プロバイダーとして使用できるようにするための SimpleSAMLphp 認証モジュールを作成しました。つまり、SAML または Shibboleth 認証をサポートする任意のサービスに対して Discourse を SSO プロバイダーとして利用できるようになります。これは非常に便利です。

ご意見をお聞かせください(コードについてコメントしたい場合は、GitHub Issues をご利用ください)。

「いいね!」 4

素晴らしいですね!モジュールをより目立たせたい場合は、#plugin:extras カテゴリにトピックを作成してみてください。このカテゴリは、Discourse 用拡張機能や統合ツールのディレクトリで、Discourse プラグイン以外のものも含んでいます。

「いいね!」 3

編集:モックアップ画像を追加


既存のウェブサイトに SSO を実装しているのですが、皆さんがユーザーに対してログイン方法をどのように「提示」しているか確認したいと考えています。

例えば、www.example.com という私のウェブサイトのトップナビゲーションに「ログイン」ボタンがあるとします。

そのログインボタンは、ユーザーを即座に Discourse のログイン認証ページへリダイレクトすべきでしょうか?それとも、まず情報ページやモーダルを表示するのが望ましいでしょうか。例えば以下のようなものです:

「これから何が起こるのか」を事前に伝えないと、ユーザーが混乱するのではないかと気になっています:thinking:

ユーザー体験やベストプラクティスについて共有できる方はいますか?

また、このスレッドの非常に詳細な最初の投稿をしてくれた @techAPJ にも感謝します。あなたの手順に従って、ASP.NET ウェブサイトをゼロから構築し、無事に実装することができました:+1:t2:

「いいね!」 2

OAuth で行われているように、変更されずに返されるステートパラメータを含めることは可能でしょうか?ASP.NET Core の認証ミドルウェアは、CSRF 攻撃を防ぐために相関 ID を生成することに依存しており、現在、これを簡単に含める方法を持っていません。

ラップする前に、コールバック URL のクエリ文字列にそれを追加することはできませんか?

例:

nonce=NONCE&return_sso_url=https://www.example.com/my/callback_url.aspx?myparam=here

@jessicah 本日試してみましたが、問題なく動作します。

        ' 戻り URL を作成
        Dim strReturnURL As String = "https://www.example.com/authtestRETURNURL.aspx?myownparametershere=surewhynot"

        ' ランダムな nonce を生成。一時的に保存し、返された nonce 値と照合できるようにします
        Dim strNonce As String = Guid.NewGuid().ToString("N")

        ' nonce と戻り URL(認証後に Discourse がユーザーをリダイレクトする先)を含む新しいペイロードを作成
        '       ペイロードの形式は次のようになります: nonce=NONCE&return_sso_url=RETURN_URL
        Dim strPayload As String = "nonce=" & strNonce & "&return_sso_url=" & strReturnURL

その後、呼び戻されるページでは、デコードされた SSO クエリ文字列内に以下が表示されます。

&return_sso_url=http%3A%2F%2Fwww.example.com%2FauthtestRETURNURL.aspx%3Fmyownparametershere%3Dsurewhynot&username=Rich

これがご意図の通りでしょうか?

「いいね!」 1

マルチサイト での discourse-auth-proxy の利用方法について

Discourse を SSO プロバイダーとして、マルチサイト認証に使用する事例や推奨事項はありますか?

マルチサイトのアプローチには、基本的には以下の 2 つがあるようです。

  1. 保護されるサイトごとに、discourse-auth-proxy のインスタンスを複数用意する。
  2. 単一の discourse-auth-proxy インスタンスを使用し、ログインリクエストの発生源に応じて、return_sso_url を含むペイロードを変更する。

どちらのアプローチも機能する可能性がありますが、課題として、異なるサイトごとに個別にログインする必要がある点が挙げられます。
また、異なるサイト(例:site1.comsite2.com)からのログインごとに、Postgres に保存されたデータが上書きされてしまうリスクもあります(Discourse の認証や PG スキーマの詳細については不明です)。

理想的なのは、一度ログインすれば、マルチサイトグループ内のすべてのサイト(例:site1.comsite2.comsite3.com)にログイン状態が共有される仕組みです。

Stack Overflow は、主にローカルセッションストレージと Iframe の組み合わせを用いてこれを実現しているようです。技術的な説明

しかし、Discourse を SSO プロバイダーとして用いてマルチサイトログインを実装した事例があれば、ぜひ知りたいと考えています。
アプローチ 1: discourse-auth-proxy の複数インスタンス
アプローチ 2: ペイロード内の return_sso_url に影響を与えるように discourse-auth-proxy を改変
アプローチ 3: アプローチ 1 または 2 を実装し、一度ログインすれば site1.com から site2.com へ移動しても再ログインが不要にする

Go 製の discourse-auth-proxy プログラムの原作者である @sam さんにタグ付けしました。

こんにちは。

SSO リターン URL におけるプロバイダーの + 記号の扱いで詰まっています。

私のケースでは、DiscourseConnect のコンシューマーは以下のような return_sso_url を作成します。

  1. 生データ: http://example.com/foo/bar?wpLoginToken=123+\\
  2. PHP で URL エンコード: http%3A%2F%2Fexample.com%2Ffoo%2Fbar%3FwpLoginToken%3D123%2B%5C

その後、DiscourseConnect は以下のようにリターンします。

  1. http://example.com/foo/bar?wpLoginToken=123%20\\\u0026sso=foo\u0026sig=bar
  2. http://example.com/foo/bar?wpLoginToken=123+\\\u0026sso=foo\u0026sig=bar

問題点は、リターン先の URL が PHP の urldecode 関数(MediaWiki 認証の核心的なワークフローです)によって処理される際、wpLoginToken の値が 123+\\ から 123 \\ 予期せぬ変化を遂げてしまうことです。

原因と思われるものを見つけました:

クライアントとサーバーがエンコード/デコードを実装しているものの、異なる仕様に従っているようです。

Discourse の変更を求めているわけではありませんが、解決策についてアドバイスをいただきたいです。

ありがとうございます。

追記:
+ 記号を二度 URL エンコードすることで解決しました。

「いいね!」 3

ありがとう!

現在試しています。GitHub であなたに2つのプルリクエストを送信しました。1つはドキュメントの更新用、もう1つは私が遭遇したバグの修正用です。

「いいね!」 1

設定 > ログイン では、DiscourseConnect の 2 つの プロバイダー 設定が連続しておらず、他の DiscourseConnect 設定と混在して表示されています:

プロバイダー設定と非プロバイダー設定は、それぞれ「Discourse で他システムの利用者を管理する」と「他システムで Discourse の利用者を管理する」という対照的なユースケースに対応しています。これらが混在して表示されると、誤設定を招く恐れがあります。2 つのプロバイダー設定を連続させ、非プロバイダー設定の前後いずれかにまとめて配置すれば、混乱を軽減できるでしょう。

「いいね!」 4

@techAPJ これを AWS Cognito と一緒に使用できますか?Discourse コミュニティ用のアプリを AWS Amplify で作成し、そのアプリを Discourse 経由で認証したいと考えています。

「いいね!」 1

遅れた返信になり申し訳ありません。おっしゃる通り、そのような混在の仕方は混乱を招きますね。この PR で整理しました:

「いいね!」 6

@mdoggydog DiscourseSsoConsumer MediaWiki 拡張機能の最近のアップデートありがとうございます。Discourse からログアウトせずにウィキからログアウトされる問題について、私たちは困惑していましたが、匿名でのウィキへのアクセスを妨げるため、$wgPluggableAuth_EnableAutoLogin は絶対に望ましいものではありませんでした。追加された $wgDiscourseSsoConsumer_AutoRelogin 設定は、まさに私たちが求めていたものでした。

「いいね!」 1

こんにちは:wave:!

元の投稿にあったPHPの例を使おうとしているのですが、データを保存する方法が理解できません。loginnonceというキーでSQLデータベースに値を保存しているだけです。SQLを使ってノンスを保存する場合、SQLデータベースは具体的にどのようになりますか?

参考になるかもしれないその他の情報として、これを使っている目的があります。DiscourseユーザーをMinecraftアカウントにリンクさせたいと考えており、UUIDに関連付けられたSSOリンクを生成します。Discourseで正常にログインした後、UUIDとDiscourse IDをテーブルに保存します。

これまでのところ、PHPの例を動作させることができましたが、私のユースケースに合わせてどのように変更する必要があるのかを完全に理解していないようです。理想的には、GETリクエストでリンクを生成し、ユーザーに送信したいので、UUIDはすでにノンスに関連付けられています。

この投稿のおかげで、もっと途方に暮れていたことでしょう。ありがとうございます!

編集:ノンスについては、テーブルにノンスを保存し、それによって検索する方が良いでしょうか?ノンスを一致させる必要があることはわかっていますが、リダイレクトURLに余分な情報を渡すことができない限り(成功していません)、ノンスを適切に参照する方法がわかりません。

「いいね!」 2

DiscourseConnect を標準 SAML プロトコルで設定するためのガイドを作成しました。SimpleSAMLphp を使用します。

「いいね!」 1

リンク詳細

/session/sso_provider?sso=bm9uY2U9NDA3YTVlNjg1ZTEwMDlh...

なぜ502 Bad Gatewayになるのかわかりません。

提供されたssoパラメータの値を一部編集しました。秘密鍵を知っている人以外は値をデコードできないはずですが、それでも完全な値を提供しない方が安全だと判断しました。これは、あなたが遭遇している502エラーとは関係ありません。

「いいね!」 1

データベースエラーのようです。同じプラグインと設定を別のDiscourseウェブサイトで試したところ、正常に動作しました。この問題をどのように解決できますか?

非ASCIIユーザー名またはグループ名(私の場合は中国語)でdiscourse-auth-proxyへの認証を試みると、Cookieにこれらの文字を含めることができないためエラーが発生するようです。これが私の修正です。(免責事項:Go言語にはあまり詳しくありません)

diff --git a/main.go b/main.go
index 1b1dc28..18f8c9e 100644
--- a/main.go
+++ b/main.go
@@ -154,7 +154,12 @@ func redirectIfNoCookie(handler http.Handler, r *http.Request, w http.ResponseWr
 		var username, groups string

 		if err == nil && cookie != nil {
-			username, groups, err = parseCookie(cookie.Value, config.CookieSecret)
+			var value string
+			value, err = url.QueryUnescape(cookie.Value)
+			if err != nil {
+				return
+			}
+			username, groups, err = parseCookie(value, config.CookieSecret)
 		}

 		if err == nil {
@@ -224,7 +229,7 @@ func redirectIfNoCookie(handler http.Handler, r *http.Request, w http.ResponseWr
 			cookieData := strings.Join([]string{username, strings.Join(groups, "|")}, ",")
 			http.SetCookie(w, &http.Cookie{
 				Name:     cookieName,
-				Value:    signCookie(cookieData, config.CookieSecret),
+				Value:    url.QueryEscape(signCookie(cookieData, config.CookieSecret)),
 				Expires:  expiration,
 				HttpOnly: true,
 				Path:     "/",
「いいね!」 1

ご報告ありがとうございます!この変更を公式バージョンにマージできるように、GitHubリポジトリに対してプルリクエストを作成していただけますでしょうか?

「いいね!」 4