Setup DiscourseConnect - Discourseの公式シングルサインオン (sso)

DiscourseConnect is a core Discourse feature that allows you to configure “Single Sign-On (SSO)” to completely outsource all user registration and login from Discourse to another site. Offered to our pro, business and enterprise hosting customers.

:information_source: (Feb 2021) ‘Discourse SSO’ is now ‘DiscourseConnect’. If you are running an old version of Discourse, the settings below will be named sso_... rather than discourse_connect_...

The Problem

Many sites wishing to integrate with a Discourse site want to keep all user registration in a separate site. In such a setup all login operations should be outsourced to that different site.

What if I would like SSO in conjunction with existing auth?

The intention around DiscourseConnect is to replace Discourse authentication, if you would like to add a new provider see existing plugins such as: Discourse VK Authentication (vkontakte)

Enabling DiscourseConnect

To enable DiscourseConnect you have 3 settings you need to fill out:

enable_discourse_connect : must be enabled, global switch
discourse_connect_url: the offsite URL users will be sent to when attempting to log on
discourse_connect_secret: a secret string used to hash SSO payloads. Ensures payloads are authentic.

Once enable_discourse_connect is set to true:

  • Clicking on login or avatar will, redirect you to /session/sso which in turn will redirect users to discourse_connect_url with a signed payload.
  • Users will not be allowed to “change password”. That field is removed from the user profile.
  • Users will no longer be able to use Discourse auth (username/password, google, etc)

What if you check it by mistake?

See: Log back in as admin after locking yourself out with read-only mode or an invalid SSO configuration

Implementing DiscourseConnect on your site

:warning: Discourse uses emails to map external users to Discourse users, and assumes that external emails are secure. IF YOU DO NOT VALIDATE EMAIL ADDRESSES BEFORE SENDING THEM TO DISCOURSE, YOUR SITE WILL BE EXTREMELY VULNERABLE!

Alternatively, if you insist on sending unvalidated emails BE SURE to set require_activation=true, this will force all emails to be validated by Discourse. WE STILL STRONGLY ADVISE THAT YOU DO NOT DO THIS, so if you proceed with that setting enabled, you are assuming substantial risk.

Discourse will redirect clients to discourse_connect_url with a signed payload: (say discourse_connect_url is https://somesite.com/sso)

You will receive incoming traffic with the following

https://somesite.com/sso?sso=PAYLOAD&sig=SIG

The payload is a Base64 encoded string comprising of a nonce, and a return_sso_url. The payload is always a valid querystring.

For example, if the nonce is ABCD. raw_payload will be:

nonce=ABCD&return_sso_url=https%3A%2F%2Fdiscourse_site%2Fsession%2Fsso_login, this raw payload is base 64 encoded.

The endpoint being called must

  1. Validate the signature: ensure that HMAC-SHA256 of PAYLOAD (using discourse_connect_secret, as the key) is equal to the sig (sig will be hex encoded).
  2. Perform whatever authentication it has to
  3. Create a new url-encoded payload with at least nonce, email, and external_id. You can also provide some additional data, here’s a list of all keys that Discourse will understand:
    • nonce should be copied from the input payload
    • email must be a verified email address. If the email address has not been verified, set require_activation to “true”.
    • external_id is any string unique to the user that will never change, even if their email, name, etc change. The suggested value is your database’s ‘id’ row number.
    • username will become the username on Discourse if the user is new or SiteSetting.auth_overrides_username is set.
    • name will become the full name on Discourse if the user is new or SiteSetting.auth_overrides_name is set.
    • avatar_url will be downloaded and set as the user’s avatar if the user is new or SiteSetting.discourse_connect_overrides_avatar is set.
    • avatar_force_update is a boolean field. If set to true, it will force Discourse to update the user’s avatar, whether avatar_url has changed or not.
    • bio will become the contents of the user’s bio if the user is new, their bio is empty or SiteSetting.discourse_connect_overrides_bio is set.
    • Additional boolean (“true” or “false”) fields are: admin, moderator, suppress_welcome_message
  4. Base64 encode payload
  5. Calculate a HMAC-SHA256 hash of the payload using discourse_connect_secret as the key and Base64 encoded payload as text
  6. Redirect back to the return_sso_url with an sso and sig query parameter (http://discourse_site/session/sso_login?sso=payload&sig=sig)

Discourse will validate that the nonce is valid, and if valid, it will expire it right away so it can not be used again. Then, it will attempt to:

  1. Log the user on by looking up an already associated external_id in the SingleSignOnRecord model
  2. Log the user on by using the email provided (updating external_id) (unless require_activation = true)
  3. Create a new account for the user providing (email, username, name) updating external_id

Security concerns

The nonce (one time token) will expire automatically after 10 minutes. This means that as soon as the user is redirected to your site they have 10 minutes to log in / create a new account.

The protocol is safe against replay attacks as nonce may only be used once. The nonce is tied to the current browser session to protect against CSRF attacks.

Specifying group membership

If the discourse connect overrides groups option is specified, Discourse will consider the comma separated list of groups passed in groups.

Aside from groups, you may also specify group membership in your SSO payload using the add_groups and remove_groups attributes regardless of the discourse connect overrides groups option.

add_groups is a comma delimited list of group names we will ensure the user is a member of.
remove_groups is a comma delimited list of group names we will ensure the user is not a member of.

Reference implementation

Discourse contains a reference implementation of the SSO class:

A trivial implementation would be:

class DiscourseSsoController < ApplicationController
  def sso
    secret = "MY_SECRET_STRING"
    sso = DiscourseApi::SingleSignOn.parse(request.query_string, secret)
    sso.email = "user@email.com"
    sso.name = "Bill Hicks"
    sso.username = "bill@hicks.com"
    sso.external_id = "123" # unique id for each user of your application
    sso.sso_secret = secret

    redirect_to sso.to_url("http://l.discourse/session/sso_login")
  end
end

Transitioning to and from single sign on.

As long as the require_activation parameter is not set to true in the request payload, the system will trusts emails provided by the single sign on endpoint. This means that if you had an existing account in the past on Discourse with DiscourseConnect disabled, DiscourseConnect will simply re-use it and avoid creating a new account.

If you ever turn off DiscourseConnect, users will be able to reset passwords and gain access back to their accounts.

Real world example:

Given the following settings:

Discourse domain: http://discuss.example.com
DiscourseConnect url : http://www.example.com/discourse/sso
DiscourseConnect secret: d836444a9e4084d5b224a60c208dce14
Email validated: No (add require_activation=true to the payload)

User attempt to login

  • Nonce is generated: cb68251eefb5211e58c00ff1395f0c0b

  • Raw payload is generated: nonce=cb68251eefb5211e58c00ff1395f0c0b

  • Payload is Base64 encoded: bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI=

  • Payload is URL encoded: bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D

  • HMAC-SHA256 is generated on the Base64 encoded Payload: 1ce1494f94484b6f6a092be9b15ccc1cdafb1f8460a3838fbb0e0883c4390471

Finally browser is redirected to:

http://www.example.com/discourse/sso?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D&sig=1ce1494f94484b6f6a092be9b15ccc1cdafb1f8460a3838fbb0e0883c4390471

On the other end

  1. Payload is validated using HMAC-SHA256, if the sig mismatches, process aborts.
  2. By reversing the steps above nonce is extracted.

User logs in:

name: sam
external_id: hello123
email: test@test.com
username: samsam
require_activation: true

Unsigned payload is generated:

nonce=cb68251eefb5211e58c00ff1395f0c0b&name=sam&username=samsam&email=test%40test.com&external_id=hello123&require_activation=true

order does not matter, values are URL encoded

Payload is Base64 encoded

bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1zYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5hbF9pZD1oZWxsbzEyMyZyZXF1aXJlX2FjdGl2YXRpb249dHJ1ZQ==

Payload is URL encoded

bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1zYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5hbF9pZD1oZWxsbzEyMyZyZXF1aXJlX2FjdGl2YXRpb249dHJ1ZQ%3D%3D

Base64 encoded Payload is signed

3d7e5ac755a87ae3ccf90272644ed2207984db03cf020377c8b92ff51be3abc3

Browser redirects to:

http://discuss.example.com/session/sso_login?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1zYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5hbF9pZD1oZWxsbzEyMyZyZXF1aXJlX2FjdGl2YXRpb249dHJ1ZQ%3D%3D&sig=3d7e5ac755a87ae3ccf90272644ed2207984db03cf020377c8b92ff51be3abc3

Synchronizing DiscourseConnect records

You can use the POST admin endpoint /admin/users/sync_sso to synchronize a DiscourseConnect record, pass it the same record you would pass to the DiscourseConnect endpoint, nonce does not matter.

If you call admin/users/sync_sso from another site, you will need to include a valid admin api_key and a valid api_username in the request’s headers. See Sync DiscourseConnect user data with the sync_sso route for more details about how to structure the request.

Clearing DiscourseConnect records

If your external_id values from your DiscourseConnect provider have changed (perhaps you changed the generation algorithm, perhaps it’s a different endpoint) you can safely remove all the existing records using the rails console:

SingleSignOnRecord.destroy_all

Logging off users

You can use the POST admin endpoint /admin/users/{USER_ID}/log_out to log out any user in the system if needed.

To configure the endpoint Discourse redirects to on logout search for the logout redirect setting. If no URL has been set here you will be redirected back to the URL configured in discourse connect url.

Search users by external_id

User profile data can be accessed using the /users/by-external/{EXTERNAL_ID}.json endpoint. This will return a JSON payload that contains the user information, including the user_id which can be used with the log_out endpoint.

Existing implementations

  • The discourse_api gem can be used for SSO. Have a look at the SSO code in its examples directory to see a basic implementation.

  • Our WordPress plugin makes it easy to configure SSO between WordPress and Discourse. Details about setting it up are found on the SSO tab of the plugin’s options page.

Future work

  • We would like to gather more reference implementations for SSO on other platforms. If you have one please post to the Dev / SSO category.

Advanced Features

  • You can pass through custom user fields by prefixing the field name with custom. For example custom.user_field_1 can be used to set the value of the UserCustomField that has the name user_field_1.
  • You can pass avatar_url to override user avatar (SiteSetting.discourse_connect_overrides_avatar needs to be enabled). Avatars are cached so pass avatar_force_update=true to force them to update if the url is the same. Right now, you can’t pass an empty url to disable users’ avatar.
  • By default the welcome message will be sent to all new users created through SSO. If you wish to suppress this you can pass suppress_welcome_message=true
  • To configure your Discourse instance as a Discourse connect provider see: Using DiscourseConnect as an identity provider.

Debugging your DiscourseConnect provider

To assist in debugging DiscourseConnect you may enable the site setting verbose_discourse_connect_logging. By enabling that site setting rich diagnostics will show up in YOURSITE.com/logs. Be sure to :white_check_mark: the warnings box at the bottom of YOURSITE.com/logs.

We will log a warning to the logs with a full dump of the SSO payload:

Last edited by @MarkDoerr 2025-06-17T19:20:06Z

Check documentPerform check on document:
「いいね!」 173

こんにちは、どこで見つけるのが難しいのですが、これはどのプロトコルを使用していますか? OAuth2 を想定していますか? パラメータが一致しないようで、プロバイダーから sso= パラメータを受け入れているように見えるというエラーが発生しますか? ヘルプ!

ありがとう

DiscourseConnect は Discourse の SSO の実装です。標準プロトコルは使用しません。

PHP コードを見てもよければ、ここに実装例があります: wp-discourse/lib/sso-provider/discourse-sso.php at main · discourse/wp-discourse · GitHub.

ええ、それはうまくいきません。ユーザー認証に使用したい OAuth2 プロバイダーがある場合は、Discourse OAuth2 Basic プラグインを見てみてください。

「いいね!」 1

@simon 様、ありがとうございます。PHPコードもプロバイダーであり、コンシューマーではないということでしょうか?また、プラグインエリアにあるOIDCプロバイダーも機能する可能性があり、「中間業者」プロバイダーも見かけました。

SSOプロバイダーが標準的でない場合、他のものと連携しないのであれば、誰/何のために意図されているのでしょうか?

重ねてお礼申し上げます!

私がリンクしたコードは、WordPressをDiscourseの認証プロバイダーとして使用するためのものです。

WordPressプラグインは、WordPressをDiscourseConnectクライアントとしても使用できるようにします: wp-discourse/lib/sso-client at main · discourse/wp-discourse · GitHub.

DiscourseにカスタムSSO実装を追加した動機は不明です。ビジネス上の理由があったのだと推測します。

その利点の1つは、外部サイトをDiscourseと緊密に統合できることです。たとえば、ここにリストされているすべてのユーザー属性は、認証プロセス中にDiscourseと同期できます: discourse/lib/discourse_connect_base.rb at 7b89fdead98606d4f47ceb0a1d240d0f6e5f589e · discourse/discourse · GitHub.

また、OAuth2またはOpenID Connectプロバイダーとして設定されていないサイトでも、Discourseでユーザーを認証するために使用できます。

欠点は、認証プロバイダーサイトにカスタムコードを追加する必要があることです。

「いいね!」 1

SSOを提供する外部サイトでメールアドレスを確認しないことによる問題について教えてください。自動スパムを可能にするだけですか?それとも他に考慮すべき点はありますか?外部サイトがメールアドレスを確認しない場合、Discourseがメールアドレス確認を処理しないことが推奨されないのはなぜですか?

追加の洞察があれば教えてください。

私が知る限り、最悪のシナリオは以下の条件を必要とします。

  • 外部サイトでメールアドレスが検証されていない
  • SSOペイロードで require_activation=true が設定されていない
  • Discourseサイトに SingleSignOnRecord が関連付けられていない既存のアカウントがある(アカウント所有者がDiscourseにSSOでログインしたことがない)

この場合、SSOでログインしたことがないDiscourseユーザーのメールアドレスを使用して、外部サイトでサインアップすることができます。これにより、外部サイトの未検証アカウントが、同じメールアドレスを使用しているDiscourseアカウントを乗っ取ることが可能になります。これは、Discourse上の管理者アカウントの場合、特に懸念されるでしょう。

外部サイトがメール検証を処理しない場合は、Discourseがメール検証を処理することが推奨されます。

ただし、メール検証を外部サイトで処理する方が良いのには、いくつかの理由があります。

  • ユーザーにDiscourseからの確認メールを受信させることは、ユーザーが初めてDiscourseにログインしようとするときのわずかな手間になります。(現実的には、その手間はどこかで発生する必要があります。Discourse側か、外部サイト側のどちらかです。)
  • SSOペイロードで require_activationtrue に設定されている場合、Discourseは既存のDiscourseアカウントをメールアドレスに基づいて外部ログインと一致させません。これは、ユーザー名/パスワードで登録してDiscourseにアカウントが作成された後にDiscourseConnectを有効にした場合に問題となります。また、何らかの理由でDiscourse上の SingleSignOnRecord エントリを削除する必要がある場合にも問題となります。ユーザーがDiscourseに再度ログインしようとしても、Discourseは自動的に新しい SingleSignOnRecord エントリを作成しません。
「いいね!」 4

ありがとうございます、@simon さん。大変参考になります!

こんにちは。SSOペイロードのgroupsフィールドについて質問があります。

管理者、モデレーター、信頼レベルなどの自動グループも上書きされますか?それとも保持されますか?

いいえ!その設定の説明が正しければ、手動グループのみに影響します。

「マニュアル」という言葉が説明になかったので、私の用途には有望そうです。試してみて、また報告します。

「いいね!」 1

これを読んだとき、Base64エンコードされたペイロードから直接署名を生成するものだと思っていました。UTF-8バイトから生成する必要があることに気づきませんでした。この点を明確にしていただけますか?

DiscourseConnectを試しています。ドキュメントは素晴らしいです、ありがとうございます。
しかし、いくつか問題にぶつかりました。いくつか助けや説明をいただけると幸いです。

WordPressのDiscourseログインでログインできるようにしたいと考えています(これはうまくいっています :))。しかし、

  • Discourseのサインアップ経由でWordPressユーザーを作成することは可能ですか?(ユーザーがDiscourseユーザーアカウントを作成したときに、自動的にWordPressアカウント/プロファイルも作成され、WordPressにログインできるようになりますか?)

  • WordPressユーザーグループとDiscourseグループを同期することは可能ですか?
    ユーザーがWordPressとDiscourseの両方のユーザーアカウントを持っている場合、DiscourseConnectはそれらを結合できますが、WordPressアカウントに同じ名前のDiscourseグループのユーザーグループを付与しません。また、その逆も同様です。(グループ名が同じでない場合はどうなりますか?ユーザーが「テスト用グループ」というDiscourseグループを持っている場合に、「テストグループ」というユーザーグループをDiscourseConnectに付与するように指示するにはどうすればよいですか?)

何が足りないのでしょうか?

わかりませんが…

注意してください。それは違法な場所であり、サイト所有者以外にとっては広く悪い習慣と見なされています(もちろん😏)、なぜならそれはユーザーの同意と認識なしに起こり、同時にデータがどこかに移動するからです。

確かに、それはいくらかグレーゾーンであり、基本的に、例えばGoogleもそれをやっています。

しかし…なぜですか? WordPress側でDiscourse SSOのみにログインを制限し、ユーザーをDiscourseにリダイレクトしてアカウントを作成すればそれで終わりです。しかし、私の知る限り、ユーザーアカウントを標準で自動的に同期することはできません。そして、なぜそうする必要があるのか、SSOがあればユーザーが必要なときに発生するからです。

私たちのシナリオでは(会員制組織として)

  • WordPressはサブスクリプションの管理、WordPressストアでの商品の購入に使用され、User Groupsを使用して組織内で会員ができることを管理しています。
  • Discourseはフォーラム/オンラインコミュニティであり、Groupsを使用してユーザーがDiscourseのどのエリアにアクセスできるかを制御しています。

現在、新規会員はWordPressアカウント(サブスクリプションなどの設定を含む)とDiscourseアカウントの両方を設定する必要があり、User GroupとDiscourse Groupは手動で管理/同期されています。

新規ユーザーが一度の設定で両方のアカウントを作成でき、User GroupとDiscourse Groupが自動的に同期されるソリューションを探しています。グループ同期についてはAPIなどで解決できると思いますが、複数のユーザーアカウント設定を解決/回避したいと考えています。

それは、DiscourseをWordPressのSSOプロバイダーとして使用しているということのようですね。そのアプローチについては、こちらに概要が記載されています。DiscourseをIDプロバイダー(SSO、DiscourseConnect)として使用する。Discourse WordPressプラグインには、WordPressをDiscourseのSSOプロバイダーとして使用するか、DiscourseをWordPressのIDプロバイダーとして使用するかを選択するオプションがあります。両方のアプローチで同じ名前を使用すると、混乱が生じます。

この場合、WordPressをIDプロバイダーとして使用することをお勧めします。このアプローチでは、ユーザーはWordPressサイトでアカウントを作成し、WordPressの認証情報でDiscourseにログインします。このアプローチで注意すべき点は、ユーザーがDiscourseにログインできるのはWordPress経由のみであり、WordPressアカウントなしでDiscourseアカウントを作成することはできないということです。DiscourseをWordPress会員サイトと統合する場合、これは適切な設定だと思います。

WordPressをDiscourseのIDプロバイダーとして使用する場合、WordPressでのアクティビティに基づいてユーザーのDiscourseグループメンバーシップを設定するのに役立つユーティリティ関数がいくつかあります。これらの関数については、こちらに概要が記載されています。WP Discourse SSOでDiscourseのグループメンバーシップを管理する

元の質問に戻ります。

WordPressプラグインのDiscourseConnectクライアントコードを確認してからしばらく経ちますが、あなたが求めていることは、そのコードが期待どおりに動作する、おおよそそのようになっていると思います。ユーザーがDiscourseアカウントを持っている場合、WordPressの「Discourseでログイン」リンクをクリックするだけでアカウントが作成されます。

これは、WordPressをDiscourseConnectクライアントとして使用する場合、技術的には可能ですが、何か変更されていない限り、ドキュメントで説明されている add_user_to_discourse_group および remove_user_from_discourse_group メソッドを使用することはできません。Discourseグループにユーザーが追加されたときにトリガーされるDiscourse Webhookを設定し、WordPressにそのWebhookを処理するコードを追加する必要があります。WordPressからDiscourseにグループを同期するには、WordPressで変更があったときにユーザーのグループを更新するためにDiscourseにAPI呼び出しを行う必要があります。したがって、WordPressをDiscourseConnectプロバイダーとして使用する場合に比較的簡単に達成できることは、WordPressをDiscourseConnectクライアントとして使用する場合にはやや複雑になる可能性があります。

「いいね!」 1

カスタムログインが使用されている場合を除きます。これは通常、WooCommerce/メンバーシップ/LLMでそのボタンが表示され、Discourse SSOのみを使用するように強制することが標準では行われず、カスタム作業が必要な状況です。

考えられる問題がいくつかあります。1つはキャッシュに関連する問題、もう1つは一部のプラグインによって追加されるログインリダイレクトに関連する問題です。これらの問題に遭遇した場合は、Support > WordPress カテゴリで質問してください。通常は簡単に解決できます。

報告するのを完全に忘れていました。説明どおりに機能し、手動グループのみが影響を受けます。

こんにちは @simon さん、

このSSO機能のPOC(概念実証)での404エラーについて、助けが必要だと思います。この問題に一日中取り組んでいますが、まだ原因を特定できていません。以下のことはできました。

  1. discourse_connect を有効化
  2. discourse_connect_url を https:/ /localhost:4200/login に設定
  3. discourse-connect secret: 20’s (1) 1111111111111111111 (単純にするため)

その後、Angularアプリのログインページで、以下のコードに従ってリクエストを送信しました。


しかし、404 Not Found という応答が返ってきました。

私の理解では、admin/users/sync_sso エンドポイントにPOSTリクエストを送信する際、ユーザーがDiscourseに存在しない場合は、user_idemail に基づいて新しいユーザーを作成し、返される結果は空のオブジェクトではなく、ユーザーオブジェクトであるべきです。ステータスコードは404ではありません。
ログも確認しましたが、この失敗した応答に関連する情報は提供されていませんでした。

よろしくお願いします!