How to Fix QQ OAuth2 Login Issue with Discourse (ERR_INVALID_REDIRECT and invalid_credentials)

Problem Description

Hello Discourse community,
We are trying to integrate QQ OAuth2 login with our Discourse instance, but we are encountering persistent issues. Specifically, we are seeing two main errors: ERR_INVALID_REDIRECT when oauth2_authorize_url and oauth2_token_url are empty, and invalid_credentials when these fields are set. We have a proxy script to handle the OAuth2 flow, but Discourse does not seem to work as expected with QQ’s API. We need help to resolve this issue.


Special Case: QQ OAuth2 Flow and API Response Format

**QQ OAuth2 Flow Requires an Extra Step
Unlike standard OAuth2 flows, QQ’s OAuth2 process requires an additional step to fetch the openid. After obtaining the access_token from the token endpoint (https://graph.qq.com/oauth2.0/token), you must make a request to the /me endpoint (https://graph.qq.com/oauth2.0/me?access_token=xxx) to get the openid. This openid is then required to fetch user information via the /user/get_user_info endpoint. The omniauth-oauth2 plugin in Discourse does not natively support this extra step, which makes integration challenging.

**QQ API Response Format **
Another issue is that QQ’s API does not return a standard JSON response for the access_token. Instead, it returns a string in the format:

access_token=xxx&expires_in=5184000&refresh_token=xxx

This non-JSON format seems to cause issues with Discourse’s omniauth-oauth2 plugin, which likely expects a JSON response (e.g., {"access_token": "xxx", "expires_in": 5184000}). This mismatch may be the root cause of the invalid_credentials error.


Case and Debugging Feedback

Here’s a detailed breakdown of our setup, the errors we encountered, and the debugging steps we’ve already taken:

  1. **Setup **

    • Discourse instance: Running in a Docker container.

    • We created a proxy script (qq_oauth_proxy.py) running on a local server to handle the OAuth2 flow, as QQ’s API requires additional steps (e.g., fetching openid and user info).

    • Initial Discourse OAuth2 settings:

      oauth2_enabled: true
      oauth2_client_id: [REDACTED]
      oauth2_client_secret: [REDACTED]
      oauth2_authorize_url: https://graph.qq.com/oauth2.0/authorize
      oauth2_token_url: https://graph.qq.com/oauth2.0/token
      oauth2_user_json_url: http://[local-server]:5001/oauth2/qq/callback?code=:code
      oauth2_fetch_user_details: true
      oauth2_json_user_id_path: id
      oauth2_json_username_path: username
      oauth2_json_email_path: email
      oauth2_scope: get_user_info
      
  2. **Error 1: invalid_credentials **

    • When oauth2_authorize_url and oauth2_token_url are set, Discourse successfully gets the access_token from QQ:

      access_token=xxx&expires_in=5184000&refresh_token=xxx
      
    • However, Discourse then fails with the following error:

      (oauth2_basic) Authentication failure! invalid_credentials: OAuth2::Error, access_token=xxx&expires_in=5184000&refresh_token=xxx
      
    • We suspect this is because QQ’s non-JSON response format is not compatible with omniauth-oauth2. Additionally, Discourse does not call our proxy script (http://[local-server]:5001/oauth2/qq/callback) after getting the access_token, which means our proxy script cannot handle the user info retrieval.

  3. Error 2: ERR_INVALID_REDIRECT / 错误 2:ERR_INVALID_REDIRECT

    • To force Discourse to rely on our proxy script, we tried setting oauth2_authorize_url and oauth2_token_url to empty.

    • However, this caused a new error when clicking the “Login with QQ” button:

      当前无法使用此页面
      [domain] 已发送了无效的响应。
      ERR_INVALID_REDIRECT
      
    • It seems that omniauth-oauth2 requires oauth2_authorize_url to initiate the OAuth2 flow, and without it, Discourse cannot generate a valid redirect URL.

  4. **Proxy Script Details **

    • Our proxy script (qq_oauth_proxy.py) works fine when tested independently. For example:

      curl "http://[local-server]:5001/oauth2/qq/callback?code=[test-code]"
      

      Returns:
      返回:

      {"access_token":"xxx","id":"xxx","username":"TDY","name":"TDY","email":null,"avatar":"http://thirdqq.qlogo.cn/[path]"}
      
    • The proxy script handles the following steps:

      1. Exchanges the code for an access_token.
      2. Fetches the openid using the access_token via the /me endpoint.
      3. Fetches user info using the access_token and openid.
      4. Returns a JSON response in the format Discourse expects.
  5. **Debugging Steps Taken **

    • Tested the proxy script independently, and it works as expected.

    • Tried setting oauth2_authorize_url and oauth2_token_url to empty to force Discourse to rely on the proxy script, but this caused ERR_INVALID_REDIRECT.

    • Added debug logging to Discourse (log_level: debug) and confirmed that Discourse does not call the proxy script when oauth2_authorize_url and oauth2_token_url are set.

    • Considered modifying the proxy script to handle the entire OAuth2 flow (including the authorize step), but we are unsure if this is the best approach.


Questions

  1. How can we make Discourse work with QQ’s non-JSON access_token response and the extra /me step? Is there a way to customize omniauth-oauth2 to handle these differences?

  2. Is there a way to configure Discourse to completely rely on our proxy script for the entire OAuth2 flow, including the authorize step?

  3. Should we create a custom OAuth2 strategy for QQ in Discourse? If so, can you provide an example of how to handle QQ’s API (including the /me step) in a Discourse plugin?

  4. Any other suggestions to resolve the ERR_INVALID_REDIRECT and invalid_credentials errors?


Additional Information

  • Discourse version: Latest stable version as of April 2025 (running in Docker).

  • Proxy script logs and Discourse debug logs are available if needed.

Thank you for your help!

1 Like