Discourse OAuth2 基础

:discourse2: Summary Discourse OAuth2 Basic supports basic OAuth2 providers, assuming they have a JSON API endpoint where user details can be retrieved by token.
:open_book: Install Guide This plugin is bundled with Discourse core. There is no need to install the plugin separately.

Features

This plugin allows you to use a basic OAuth2 provider as authentication for Discourse. It should work with many providers, with the caveat that they must provide a JSON endpoint for retrieving information about the user you are logging in.

This is mainly useful for people who are using login providers that aren’t very popular. If you want to use Google, Facebook or Twitter, those are included out of the box and you don’t need this plugin. You can also look for other login providers in our Github Repo.

Configuration

Basic Configuration

  1. First, register your Discourse application with your OAuth2 provider. It will require a Redirect URI which will be:

    http://DISCOURSE_HOST/auth/oauth2_basic/callback

:information_source: Replace DISCOURSE_HOST with the appropriate value, and make sure you are using https if enabled. The OAuth2 provider should supply you with a client ID and secret, as well as a couple of URLs.

  1. Visit your AdminSettingsOAuth2 Login and fill in the basic configuration for the OAuth2 provider:
  • oauth2_enabled - check this off to enable the feature
  • oauth2_client_id - the client ID from your provider
  • oauth2_client_secret - the client secret from your provider
  • oauth2_authorize_url - your provider’s authorization URL
  • oauth2_token_url - your provider’s token URL.

:information_source: If you can’t figure out the values for the above settings, check the developer documentation from your provider or contact their customer support.

Configuring the JSON User Endpoint

Discourse is now capable of receiving an authorization token from your OAuth2 provider. Unfortunately, Discourse requires more information to be able to complete the authentication.

We require an API endpoint that can be contacted to retrieve information about the user based on the token.

For example, the OAuth2 provider SoundCloud provides such a URL. If you have an OAuth2 token for SoundCloud, you can make a GET request to https://api.soundcloud.com/me?oauth_token=A_VALID_TOKEN and will get back a JSON object containing information on the user.

To configure this on Discourse, we need to set the value of the oauth2_user_json_url setting. In this case, we’ll input the value of:

https://api.soundcloud.com/me?oauth_token=:token

The part with :token tells Discourse that it needs to replace that value with the authorization token it received when the authentication completed.

There is one last step to complete. We need to tell Discourse what attributes are available in the JSON it received. Here’s a sample response from SoundCloud:

{
  "id": 3207,
  "permalink": "jwagener",
  "username": "Johannes Wagener",
  "uri": "https://api.soundcloud.com/users/3207",
  "permalink_url": "http://soundcloud.com/jwagener",
  "avatar_url": "http://i1.sndcdn.com/avatars-000001552142-pbw8yd-large.jpg?142a848",
  "country": "Germany",
  "full_name": "Johannes Wagener",
  "city": "Berlin"
}

The oauth2_json_user_id_path, oauth2_json_username_path, oauth2_json_name_path and oauth2_json_email_path variables should be set to point to the appropriate attributes in the JSON.

The only mandatory attribute is id - we need that so when the user logs on in the future that we can pull up the correct account. The others are great if available – they will make the signup process faster for the user as they will be pre-populated in the form.

Here’s how I configured the JSON path settings:

  oauth2_json_user_id_path: 'id'
  oauth2_json_username_path: 'permalink'
  oauth2_json_name_path: 'full_name'

I used permalink because it seems more similar to what Discourse expects for a username than the username in their JSON. Notice I omitted the email path: SoundCloud do not provide an email so the user will have to provide and verify this when they sign up the first time on Discourse.

If the properties you want from your JSON object are nested, you can use periods. So for example if the API returned a different structure like this:

{
  "user": {
    "id": 1234,
    "email": {
      "address": "test@example.com"
    }
  }
}

You could use user.id for the oauth2_json_user_id_path and user.email.address for oauth2_json_email_path.

If the key itself includes periods, you will need to put double quotes around it, or escape the periods with a backslash. For example, given this JSON:

{
  "example.com/uid": "myuid"
}

You would specify the path as example\.com/uid or "example.com/uid"

:warning: If you set oauth2_json_email_path, the OAuth2 provider must confirm the user owns that email address. Failure to do this can result in account takeover in Discourse!

:discourse2: Hosted by us? This plugin is available on our Business and Enterprise plans. OAuth 2.0 & OpenID Connect Support | Discourse - Civilized Discussion

Last edited by @tobiaseigen 2025-07-16T21:39:12Z

Check documentPerform check on document:
28 个赞

你好,

我们正尝试使用 OAuth2 Basic 将 Discourse 与我们的应用程序集成,但在日志中遇到以下错误:
注意:我们在调试连接时使用了 NGROK。

OAuth2 Debugging: request POST https://formshare.ngrok.io/oauth2/token

Headers: {"User-Agent"=>"Faraday v1.9.3", "Content-Type"=>"application/x-www-form-urlencoded", "Authorization"=>"Basic S2k2SFZtTVpuSTFHUExiRXVlWVJDN4CbOkNvb1k0anlQemt3dWNRV21Sa2FWOVNnbHZLbjJFT3cxc3BIMmtMck9yY21vNDM4Tg=="}

Body: {"client_id"=>"Ki6HVmMZnI1GPLbEueYRC4Cb", "client_secret"=>"...some_secret_...", "grant_type"=>"authorization_code", "code"=>"5pPCrsp0pZ84373MNaHh2cuskfc8AlbfmdwMBFIVW4n4z9aX", :redirect_uri=>"https://community.formshare.org/auth/oauth2_basic/callback"}

------------------

OAuth2 Debugging: response status 200

From POST https://formshare.ngrok.io/oauth2/token

Headers: {"content-length"=>"108", "content-type"=>"text/html; charset=UTF-8", "date"=>"Thu, 01 Sep 2022 21:42:08 GMT", "ngrok-trace-id"=>"79cdc3f1c3eae5e37a30796aebbf9bd6", "server"=>"gunicorn"}

Body: {"token_type": "Bearer", "access_token": "p0FVuwjSXL1ZINEklMAVqUlpZxSll1SgnbpE8YWP4C", "expires_in": 864000}

-----------------------------------

(oauth2_basic) Authentication failure! invalid_credentials: OAuth2::Error, {"token_type": "Bearer", "access_token": "p0FVuwjSXL1ZINEklMAVqUlpZxSll1SgnbpE8YWP4C", "expires_in": 864000}

我们留空了“oauth2 callback user id path”和“oauth2 callback user info paths”这两个参数。

有什么想法吗?

我可以使用此方法向 Microsoft 的 XBL 服务进行身份验证吗?

我猜逻辑会与此类似?

大家好。我尝试使用我们的内部 Oauth2 服务器和授权码流来配置此插件。

当用户点击“使用 Oauth 连接”时,/authorize 端点会正常工作,并且会向回调返回一个 code。但之后 Discourse 会显示一个通用的 500 错误“哎呀。驱动此讨论论坛的软件遇到了意外问题”,并且不会访问 /token 端点。

错误日志显示如下
OAuth2::ConnectionError (FinalDestination: 所有已解析的 IP 都被禁止) lib/final_destination/ssrf_detector.rb:74:in lookup_and_filter_ips' lib/final_destination/http.rb:13:in connect’ lib/midd

hostname discourse-app
process_id 653
application_version 702f27e6ee10ac257f5fee3f331d05f5fa5d7a45
HTTP_HOST *****
REQUEST_METHOD GET
HTTP_USER_AGENT Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
HTTP_ACCEPT text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
HTTP_REFERER *****
HTTP_X_FORWARDED_FOR *****
HTTP_X_REAL_IP *****
time 晚上 10:25
params
code def50200babf84f7376f99fefa34369d876566b6bc0a341d8fba431999a72549ac06f6aad01df6fa43061707c525ba5d725ad
state 20139e0a134a5972566d4ddb6f7f9092a2cddb9e5216973a

据我所知,这是某个 IP 地址的问题?目前 Oauth2 服务器托管在我的开发环境中(localhost),并且授权和令牌端点已相应配置。有问题吗?

问题已找到:
1)出于某种原因,/token 端点从未被调用。在管理员参数中填写了与 oauth 相关的最多选项后,该端点被调用但没有响应。
2)我忘记了是 Discourse 服务器会调用 /token 端点,而不是 webclient。因此,服务器无法访问我的 localhost Oauth2 服务器。将我们的 Oauth2 服务器置于域名之后解决了问题。

现在,我可以连接现有用户,但不明白如何通过此插件登录新用户。
如果用户使用 oauth 登录,会收到一条错误消息,提示他在 Discourse 服务器上没有活动帐户。这很正常,因为这是一个新用户。

是否有专门的回调用于登录用户而不是注册用户?或者是否有特定的参数可以设置以允许创建帐户?

我的公司 oauth 服务器生成的 /profile JSON 响应中的一个字段有一个小拼写错误。修复拼写错误后一切都正常了。
但我不得不说 Discourse 的日志可能非常误导人!回调没有任何问题。

大家好:

我在从授权响应中提取用户 JSON 请求所需的 ID 时遇到问题。根据文档,似乎账户 ID 嵌套在数组中:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
   "access_token":"2YotnFZFEjr1zCsicMWpAA",
   "token_type":"Bearer",
   "expires_in":1800,
   "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
   "permissions":[
      {
        "accountId":123,
        "availableScopes":["contacts_view", "contacts_me",
"contacts_edit", "finances_view", "events_view"]
      }
   ]
}

我尝试将 oauth2 回调用户 ID 路径设置为 permissions[0].accountId,但我的 uid 值始终为空。不幸的是,提取用户 JSON 的调用需要在 JSON URL 中包含此 accountId。

我通过传递 permissions.first.accountId 成功解决了这个问题。我发现当我在测试属性中传递 permissions 时,该数组已被解析为 Ruby 数组。不幸的是,字段似乎拒绝了调用数组元素的 Ruby 语法,并且任何使用 Javascript 语法的尝试都会导致“TypeError String to Integer”错误。幸运的是,Ruby 有上述语法,这是预期的方法吗?

我刚用 Authentik OAuth2 实现了这个功能,但在 oauth2 user json url 设置上遇到了一些麻烦。我使用了 Authentik 的 user_info 端点(/application/o/userinfo/),但我不知道如何映射字段。对于任何想了解如何设置 Discourse 与 Authentik 的 OAuth2 的人,这里是总结:

  • 用户 ID 路径:preferred_username
  • 用户名路径:preferred_username
  • 姓名路径:name
  • 邮箱路径:email
  • 邮箱已验证路径:email_verified
  • 头像:空。

我遇到了以下问题:

  1. 一开始,我在 JSON URL https://DOMAIN/application/o/userinfo/ 中忘记了末尾的斜杠。这导致用户信息请求(源永久链接)返回 301 HTTP 状态码,导致登录失败。我不知道按规范是否应该有末尾的斜杠,但也许应该正确处理 301。
  2. 调试这个过程很棘手。oauth2 debug auth 设置非常有用,但是……Logster 会在实际转储有意义的响应数据之前截断调试日志。我不得不手动修改容器中的日志行:
    log("user_json_response: #{user_json_response.status} #{user_json_response.headers} #{user_json_response.body}")
    
    也许可以更新那行日志?我想这可以帮助其他人找出 JSON 属性路径。
4 个赞

我刚通过插件设置了 Auth0,发现头像没有被抓取。

以下是相关的设置:

  DISCOURSE_OAUTH2_ENABLED: true
  DISCOURSE_OAUTH2_CLIENT_ID: '${DISCOURSE_OAUTH2_CLIENT_ID}'
  DISCOURSE_OAUTH2_CLIENT_SECRET: '${DISCOURSE_OAUTH2_CLIENT_SECRET}'
  DISCOURSE_OAUTH2_AUTHORIZE_URL: '${DISCOURSE_OAUTH2_ISSUER}/authorize?connection=xxx&login_options=yyy'
  DISCOURSE_OAUTH2_TOKEN_URL: '${DISCOURSE_OAUTH2_ISSUER}/oauth/token'
  DISCOURSE_OAUTH2_USER_JSON_URL: '${DISCOURSE_OAUTH2_ISSUER}/userinfo'
  DISCOURSE_OAUTH2_SCOPE: 'email openid profile'
  DISCOURSE_OAUTH2_JSON_USER_ID_PATH: 'sub'
  DISCOURSE_OAUTH2_JSON_USERNAME_PATH: 'nickname'
  DISCOURSE_OAUTH2_JSON_NAME_PATH: 'name'
  DISCOURSE_OAUTH2_JSON_EMAIL_PATH: 'email'
  DISCOURSE_OAUTH2_JSON_EMAIL_VERIFIED_PATH: 'email_verified'
  DISCOURSE_OAUTH2_JSON_AVATAR_PATH: 'picture'
  DISCOURSE_OAUTH2_EMAIL_VERIFIED: true
  DISCOURSE_OAUTH2_OVERRIDES_EMAIL: true
  DISCOURSE_OAUTH2_ALLOW_ASSOCIATION_CHANGE: false

在调试日志中,我可以看到 JSON 响应中设置了 picture 元素,但用户的头像没有改变,无论是新用户还是现有用户。

我错过了什么?

如何用另一个图标或图像替换登录按钮上的图标是最佳方法?

.btn-social.oauth2_basic:before {
    content: url('https://www.contoso.com/path/to/image');
}

.btn-social.oauth2_basic > svg {
    display: none;
}

感觉足够了,但有点取巧

2 个赞

该插件似乎只在用户首次创建时更新头像/用户名,而不是在每次登录时更新。

有什么方法可以修复此问题,并在登录/重新连接时让插件也更新头像吗?

您可以使用 auth overrides emailauth overrides usernameauth overrides name 设置使这些内容在将来的登录中生效。恐怕我们目前没有类似的头像设置,但我们非常欢迎您提交拉取请求(pr-welcome)。

2 个赞

谢谢!我后来找到了这些。我 fork 了这个仓库,并添加了我自己的版本,使其能够与 Roblox 配合使用,其中包括头像的覆盖。我相信这只是使用了 DiscourseConnect 的头像覆盖,所以人们无法更改它。

我希望的一件事是 Roblox 不会在 OAuth 中提供电子邮件,所以我很遗憾需要让他们使用电子邮件注册。但这对于你们来说不是问题,哈哈。

帖子已拆分为新主题:Twitter 登录无法在 Meta 上使用

有人知道这个是否仍然有效吗?

是的。我相信这个插件能正常工作。 :+1:

1 个赞

您好,我已成功将此插件集成到我的 discourse discuss.frontendlead.com 中,我正在使用 teachable OAuth https://docs.teachable.com/docs/oauth-quickstart-guide

但是,我只想允许那些在 teachable 上拥有当前付费账户的人成功注册。我想象中,我需要在此插件中添加自定义功能来处理这个问题?我想知道,你们或者我自己是否可以,在 OAuth 之后在设置中引入另一个名为“自定义代码”的字段,允许开发人员在注册后执行特定操作?或者是否有更好的建议,请告诉我。

编辑:我 fork 了该仓库,并在以下位置使其正常工作:

如果其他使用 teachable 的人也在尝试做同样的事情,我的仓库将开箱即用,唯一的问题是如果您没有购买课程,它会提示您需要访问我的域名进行购买。您可能希望根据自己的用例更新此信息。

2 个赞

太棒了!

Discourse Patreon 插件的 OAuth2 注册也存在类似情况。当启用“使用 Patreon 登录”时,它允许任何拥有 Patreon 账户的人在 Discourse 网站上注册。网站所有者通常希望只允许 他们的 支持者注册 Discourse 账户。我想知道 Patreon 是否会返回允许将类似逻辑添加到 Patreon 身份验证中的详细信息?

1 个赞

我遇到了和上面 @qlands 完全一样的错误。

我最初的计划是将配置文件信息发送到 token 中。看到它不起作用,我将其剥离,缩进以尝试 JSON 方法。但它甚至没有到达调用 JSON 文件的地步。

错误消息是:

(oauth2_basic) 身份验证失败!invalid_credentials: OAuth2::Error, {
  "access_token":"fa79b6fe0763862f5a8fd8",
  "token_type":"Bearer",
  "expires_in":3600,
  "scope":"profile"
}

您认为上述回复有什么问题吗?
为什么 OAuth2 服务器回复了 200 并附带 token,而插件却生成了 invalid_credentials 错误?