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

Discourse をご自身の Web アプリケーションのアイデンティティプロバイダーとして使用したいですか?素晴らしいですね!始めましょう。

DiscourseConnect プロバイダー設定の有効化

Discourse 管理サイト設定(/admin/site_settings)で、設定項目 enable discourse connect provider を有効にし、SSO ペイロードのハッシュ化に使用されるシークレット文字列を discourse connect provider secrets に追加します。

Web アプリケーションへの DiscourseConnect の実装:

  • ランダムな nonce を生成します。この値を NONCE と呼びます。応答で返される nonce 値と検証できるように、一時的に保存します。

  • NONCERETURN_URL(Discourse が検証後にユーザーをリダイレクトする場所)を持つ新しいペイロードを作成します。ペイロードは次のような形式である必要があります: nonce=NONCE&return_sso_url=RETURN_URLRETURN_URL のホストは、discourse connect provider secrets の設定時に使用したドメインパターンと一致する必要があります。

  • 上記の生のペイロードを Base64 エンコードします。このペイロードを BASE64_PAYLOAD と呼びます。

  • 上記の BASE64_PAYLOAD を URL エンコードします。このペイロードを URL_ENCODED_PAYLOAD と呼びます。

  • sso プロバイダーシークレットをキーとして BASE64_PAYLOAD から HMAC-SHA256 シグネチャを生成し、そこから小文字の 16 進数文字列を作成します。このシグネチャを HEX_SIGNATURE と呼びます。

Discourse への認証リクエストの送信

ユーザーを DISCOURSE_ROOT_URL/session/sso_provider?sso=URL_ENCODED_PAYLOAD&sig=HEX_SIGNATURE にリダイレクトします。

Discourse からの応答の取得:

上記の手順が正しく実行されると、Discourse はログインしたユーザーを指定された RETURN_URL にリダイレクトします。クエリ文字列パラメーターとして sigsso、およびいくつかのユーザー情報を受け取ります。次に、以下の手順に従います。

  • sso プロバイダーシークレットをキーとして sso の HMAC-SHA256 を計算します。

  • sig を 16 進数表現からバイトに変換します。

  • 上記の 2 つの値が等しいことを確認します。

  • sso を Base64 デコードします。埋め込まれたクエリ文字列が得られます。これには nonce というキーが含まれており、その値は元々渡された nonce と一致する必要があります。これが一致することを確認し、システムから nonce を削除するようにしてください。

  • このクエリ文字列には多くのユーザー情報も含まれていることがわかります。必要に応じて使用してください。

これで完了です。これで、Web アプリケーションが Discourse を SSO プロバイダーとして使用するように設定できました!

その他のパラメーター、その他のオプション

noncereturn_sso_url に加えて、リクエストペイロードにはいくつかの追加のオプションパラメーターがあります。

  • prompt: prompt=none の場合、SSO リクエストは「確認のみ」のリクエストとして扱われます。ブラウザ/デバイスがすでに Discourse にログインしている場合、Discourse は通常どおり認証情報を含む成功した SSO 応答を返します。ブラウザ/デバイスがまだログインしていない場合、Discourse はユーザーにログインを求めず、ユーザー情報を含むのではなく、代わりに failed=true パラメーターを含む SSO 応答を直ちに返します。これは、ログインダイアログを表示せずにユーザーがログインしているかどうかを確認するためのメカニズムを提供します。

  • logout: logout=true の場合、SSO リクエストはログアウトリクエストになります。そのブラウザ/デバイスでユーザーが Discourse にログインしている場合、そのデバイスからログアウトされます。いずれの場合も、Discourse は ssosig をクエリ文字列に追加せずに、直ちに return_sso_url にリダイレクトします。

  • require_2fa: require_2fa=true の場合、Discourse はリダイレクトバックの前にユーザーに二要素認証(2FA)の確認を要求します。応答ペイロードには、ユーザーが 2FA 検証を正常に完了した場合に confirmed_2fa=true が含まれるか、ユーザーが 2FA メソッドを何も設定していない場合に no_2fa_methods=true が含まれます。

prompt=nonelogout=true は相互に排他的です。同じリクエストで両方を提供することは意味がありません。

sso= ペイロードリファレンス

リクエストパラメーター:

  • nonce: (文字列、必須) 安全に生成されたランダムな文字列
  • return_sso_url: (文字列、必須) 応答とともにリダイレクトバックする URL
  • prompt: (文字列、オプション) none の場合、ユーザーにログインを促さずに認証状態を問い合わせます。
  • logout: (ブール値、デフォルト false) true の場合、Discourse からユーザーをログアウトさせます。
  • require_2fa: (ブール値、デフォルト false) true の場合、リダイレクトバックの前にユーザーに二要素認証の確認を要求します。

結果パラメーター:

  • ログアウトリクエストへの応答には、sso= ペイロードもシグネチャも含まれず、リクエストのプレーンな return_sso_url へのリダイレクトのみが行われます。

  • ログインリクエストの結果ペイロードには、リクエストから反映された nonce が常に含まれます。

  • 結果ペイロードは、その他のリクエストパラメーターも反映します。この動作に頼らないでください。必ずしも意図されたものではなく、API の保証された側面ではありません。(例:なぜ return_sso_url パラメーターが return_sso_url に送信されるペイロードにコピーされるのですか?)

  • リクエストがユーザーの認証に失敗した場合、結果ペイロードには failed=true が含まれます。

  • リクエストがユーザー認証に成功した場合、結果ペイロードにはユーザーの資格情報/情報が含まれます:

    • external_id: (文字列) Discourse ユーザー ID
    • username: (文字列) ユーザー名/ハンドル
    • name: (文字列) ユーザーの本名
    • email: (文字列) メールアドレス
    • avatar_url: (文字列) ユーザーがアップロードしたアバター画像の完全な CDN URL
    • admin: (ブール値) ユーザーが管理者であれば true、そうでなければ false
    • moderator: (ブール値) ユーザーがモデレーターであれば true、そうでなければ false
    • groups: (文字列) ユーザーが属するグループ名(名前順)のコンマ区切りリスト
    • profile_background_url: (文字列) ユーザーのプロフィール背景画像の完全な CDN URL
    • card_background_url: (文字列) ユーザーのカード背景画像の完全な CDN URL
    • confirmed_2fa: (ブール値) ユーザーが 2FA 検証を完了した場合に true ( require_2fa=true が要求された場合にのみ存在します)
    • no_2fa_methods: (ブール値) ユーザーが 2FA メソッドを設定していない場合に true ( require_2fa=true が要求された場合にのみ存在します)

    nameavatar_urlprofile_background_urlcard_background_url は、ユーザーがこれらを設定していない場合は省略されることがあります。(Discourse 内で nil の値を持つ要素は応答から除外されます。)

Discourse 公式の「Discourse をアイデンティティプロバイダーとして使用する」実装:

コミュニティ提供の「Discourse を SSO プロバイダーとして使用する」実装:

「いいね!」 63
Use discourse for SSO in a non-web app?
Can I require a Discourse login to view things on a different site?
SSO Redirect Problem
How-to setup a Discourse network?
Setting the session token '_t' on the entire domain, not just my subdomain
Login to Discourse with custom Oauth2 provider
Can I log into multiple instances of discourse simultaneously?
Reverse Single-Sign-On: Possible to use Discourse accounts on my other site?
Inter-Discourse
Login via discourse
Discourse a SSO provider for Wordpress
How to use SSO (Discourse on subdomain)
Discourse as an identity provider not working in localhost
Validate User Session on another custom site
Use the same user database and login credentials in multiple discourse instances
Login w/ Discourse w/o SSO?
Best way to use discourse as SSO - Wikimedia, Nextcloud, Immich
DiscourseConnect provider does not redirect to return_url after entering correct id and password
[Paid?] Migrate comments from several Blogger & Wordpress blogs to Discourse, under an SSO
Login error while trying to use Discourse as an identity provider (SSO, DiscourseConnect)
Is it possible?
Use Discourse SSO with Mediawiki
Use Discourse SSO with Mantis Bug Tracker
Use Discourse API to check whether username/password combination is valid
How to create a login on my front-end application to a specific Discourse site?
When Discourse is an identity provider, does it save the external user IDs?
How can I change the registration URL?
Should Discourse make an effort to become a viable comment platform?
Game Dev - User Registration & DB management (advice needed)
Game Dev - User Registration & DB management (advice needed)
Security/Privacy concern: Email exposed in DiscourseConnect Provider redirect URL
System deleting users: 'inactive user', 'Automatically deleted as abandoned, deactivated account', and 'staged unused'
Checking whether a user is logged in on Discourse from another website
Communities using discourse SSO for their in-app community experience
OAUTH flow to Integrate the discourse community account with the third party CRM tool where it can create tickets of community
OAUTH flow to Integrate the discourse community account with the third party CRM tool where it can create tickets of community
Setup DiscourseConnect - Official Single-Sign-On for Discourse (sso)
Is it Possible to Send Encrypted Email and Password in the Authentication Flow?
How Discourse ID works
Discourse doesn’t redirect to return_sso_url after user logs in on private site
Discourse as an SAML, OAUTH IDP?
What to do with response from Discourse SSO as provider
Discourse as SSO provider for Prosody
[Paid] WP + Discourse user authentication for IRC or other chat
User API keys specification
Discourse session variables
Enabling Login to Discourse from a third party api
How to use SSO (Discourse on subdomain)
Return_sso_url is blank but provided
Set up your DiscourseConnect(DiscourseSSO) to SimpleSAMLphp, use your Discourse forum as a SAML IDP (Identify Provider)
Discourse doesn't redirect to return_sso_url after user logs in on private site
Can Discourse be used as an OAuth provider?
Log in to Rocketchat with Discourse?
Authorization from a desktop application (and base domain site)
Can two Discourse Instances share and use One Database?
Nil secret on SSO
Can I authenticate to Drupal via Discourse?
Using Discourse as an authentication provider
Triggering account creation/login on external service when a user logs in on discourse
Using Discourse as SSO for desktop app
Triggering account creation/login on external service when a user logs in on discourse
Getting more discourse user data when using discourse as identity provider
Discourse as SSO Provider: Login Error
SSO between two Discourse instances
Could Discourse offer a StackExchange-like SSO/Federated login service?
Confirming a correct User+Pass combination via API?
How can I authenticate 3rd party website using discourse?
Share sign-up and login between Discourse and site?
How to configure Discourse as the sole user-facing login/sign-up provider for Wordpress site
Discourse SSO Provider doesn't redirect to return_sso_url as user logs in with custom SSO
OAuth 2 and other discourse sites?
Can I use the Discourse API to authenticate users in another app?
How can generate _forum_session and _t for an user through code/api call or without login to browser?
Setup DiscourseConnect - Official Single-Sign-On for Discourse (sso)

Great how to, thanks.

FYI, the term ‘nonce’ has an unfortunate meaning in Britain.

「いいね!」 10

I wrote a class to implement the process in Ruby. I use it to work with devise in my web application.

https://github.com/gogo52cn/sso_with_discourse

「いいね!」 3

sso seems to have a trailing newline which needs to be included when sent to the HMAC function, so it’s important to make sure that SSO consumer applications don’t strip whitespace from these query arguments.

「いいね!」 1

Our app users get their own subdomains. Is there a way to implement this with variable SSO urls?

interesting, this is tricky you would need to monkey patch this in, or better still make core extensible in this way and add a plugin.

「いいね!」 3

Hello,

I have created an Erlang implementation for encoding/decoding the payloads described in this specification. You can find it here:
https://github.com/reverendpaco/discourse-as-sso-erlang
Feedback is welcome.

Thanks to @mpalmer on another thread for setting me straight.

As I put together this implementation, I realized that the specification does not say too much about a few things:

  1. the user information that is returned in the query parameters,
  2. what happens when a user is not found

When I got around to testing I found the answers, and then confirmed by looking at the code, here: discourse/app/controllers/session_controller.rb at main · discourse/discourse · GitHub

I shall assume that in regards to 1. that while new user information may be added, that none of these values (“name”,“username”,“email”,“external_id”, etc) will be removed. This is just as important, contractually as what is described in the main post.

One piece of feedback I’d like to give the Discourse team is that it would be nice to add a means to optionally return back to the calling application in the case of a missing user.

Currently, at line 51 a non-logged-in or non-registered user will be forwarded to the Discourse login page. While this can be useful, I would rather programmatically have the option to learn that this person has not yet logged in (or registered) and give them the opportunity on my site to continue anonymously.

I can imagine something like this:

DISCOURSE_ROOT_URL/session/sso_provider?
sso=URL_ENCODED_PAYLOAD&sig=HEX_SIGNATURE&
returnBackIfUserMissing=true

and then the Discourse site sending back to the return_sso_url with either a special header, or an attribute, rather than redirecting to /login.

This change should be backwards compatible.
I’m new here, so if this is something that could be contributed via a pull-request, please tell me and I could take a shot at it next week.

thanks,
daniel

「いいね!」 5

Sure, totally open to add another option to the payload you send us for create_new=false PR welcome.

「いいね!」 2

added a PR for this

Commit-Message:

Implemented an optional 'no_user_found_return_sso_url' parameter to be called
 by the client when client is using Discourse as an SSO and wants Discourse to 
redirect back to a place of the client's choosng when a user is not found.

Currently, the Discourse as an SSO implementation checks the cookies _t and
 _session_forum to see if the user is registered and present in the database 
(_t is the token that is located in the users table). If a user is not found, 
the current implemenation forwards to the forum's /login URL. This behavior 
may be what the client wants, but it would be good to give an option to the 
client to send somewhere else.

This commit allows the client to embed an optional 'no_user_found_return_sso_url' 
parameter in the payload, prior to base64 and URL-encoding. If the Discoure SSO
endpoint detects that this parameter is present in the payload (and has a non-empty
value) the Discourse server will redirect to this new location if it does not detect the 
user. If this parameter is not present, then the redirection to /login will take place 
as it currently does.

Additionally, as the client may choose to use the same URL for
'no_user_found_return_sso_url' as for 'return_url', this commit introduces a new 
query-string name-value pair to be sent back to the client 'no_user_found_return_sso_url' 
location. This parameter 'user_found' will ALWAYS be sent back to the client, either when 
the user is found and 'return_url' is used or when the user is not found 
'no_user_found_return_sso_url' is used (values will be 'true' and 'false' respectively).

thanks,
daniel

「いいね!」 4

Nice work :slight_smile:

redirect_to can be routed to a url with parameters. But isn’t a create_new=false enough? Or whatever name it is. You’ll get nonce and the flag back.

I wanted to give the client the flexibility of sending a failure to a different URL than the success url (return_url). With complicated SSO architectures, this might be a requirement. (Selfishly, I wanted to make my return endpoint code less convoluted – i.e. on my side I have two codebases at /sso for success and /sso_failure for failure).

Implicitly there are already two URLs (return_url for success and ‘/login’ for failure) – I didn’t want to lose that.

So just to be clear, the payload you send to the Discourse endpoint should have both return_url and no_user_found_return_sso_url if you want Discourse to send it back when no user is found. It should have return_url only if you are ok with Discourse forwarding to /login.

Should look like this:

1.If you want Discourse to forward to login if no user found, then:

PAYLOAD = return_url=mydomain.com/clientendpoint&nonce=xE787euK
2. If you want Discourse to send back to you to the same endpoint for failure as success:

PAYLOAD = no_user_found_return_sso_url=mydomain.com/clientendpoint&return_url=mydomain.com/clientendpoint&nonce=xE787euK
3. If you want Discourse to send back to you to a different endpoint as success:

PAYLOAD = no_user_found_return_sso_url=sub.mydomain.com/handleNewUser&return_url=mydomain.com/clientendpoint&nonce=xE787euK

Potentially a bit over-engineered, but that was my thought process.

「いいね!」 1

To be clear, it’s not user_not_found, it’s not logged in.

It’s about protocol not engineering in the first place. Given two endpoints, you have to deal with two possible endpoints. In both cases you must validate the nonce and destroy it. It’s a hurdle not benefit.

In extreme case, a client can ask Discourse several times with/without session (current_user), thus you would get n responses in each endpoints. It would be twice as hard to secure it due to changing cookies on your end.

「いいね!」 2

Completely agree on protocol over engineering. Hence, my submission.

The semantics are equivalent to an if-else block, or more generally a case/switch block. result_url is our true condition, and user_not_found_url is our else block. If we didn’t provide it, we would force the client to have to deal with the ‘failure’ condition in the same codebase/endpoint – and for 90% of the people, this will be fine.

90% of people who want Discourse to return back to them will set return_url and user_not_found_url to be exact same thing. This bears repeating, and really renders the motivations for the 10% moot.

90% of the time you will make return_url and user_not_found_url the exact same thing. These 90%-ers will use their JSESSIONID/ PHPSESSID/ ASPSESSIONID /_session to look up the returned encoded nonce in their framework-supplied session-store and engineer accordingly.

7% of people will be happy to pass it to a different URL on their same app-server, which does some decoration (servlet-chains anyone?) or routing, but still looks up the nonce in their session.

3% will have some complicated polyglotish system that uses a distributed session store (like memcache) to store sessions for different app servers implemented in different legacy codebases. It’s up to them to store/invalidate the nonce across these different systems.

I realize I might not have been completely clear, but the user_not_found_url still receives the sso and sig parameters, just like the return_url.

So, if you are the 90% scenario, when you get the payload and verify/decode it, you will find the parameter user_found=false|true to know why it’s coming back to you.

「いいね!」 2

@fantasticfears, as per your recommendation on the PR, I have renamed the attribute from user_not_found_url to return_sso_unlogged_in_url.

「いいね!」 1

3 posts were split to a new topic: Can Discourse be used as an OAuth provider?

Great job !

Do you know how it possible to adapt it to GitLab in order to allow the Discourse users to directly login there?

「いいね!」 2

Hey folks, I could not find anything, so I created a npm for Discourse auth using Passport for node.js.

It’s here, and in npm if anyone needs it:

(really appreciate the team’s work, we’ve been a user since the very early days @ talk.wigwag.com)

「いいね!」 5

@sam,

I have submitted a reply back to your rejection of the PR:
Optional 'no_user_found_return_sso_url' parameter for using Discourse as SSO by reverendpaco · Pull Request #4211 · discourse/discourse · GitHub

As I have been relying on my patched version of discourse for these months, I would be interested in convincing you as to my need.

If this PR is going to be rejected, could you explain what I can do to get the functionality that I need?

Does anyone know of a wordpress plugin that works with discourse as the SSO provider?

「いいね!」 1