Security/Privacy concern: Email exposed in DiscourseConnect Provider redirect URL

Describe the bug

When using Discourse as a DiscourseConnect Provider (SSO Provider), the user’s email address is exposed in the 302 redirect URL to the relying party. This happens because the populate_user_data method in lib/second_factor/actions/discourse_connect_provider.rb always sets the email:

  def populate_user_data(sso)
    sso.name = current_user.name
    sso.username = current_user.username
    sso.email = current_user.email  # <-- Always included
    sso.external_id = current_user.id.to_s
    # ...
  end

This email is thenBase64-encoded and included in the redirect URL:
https://target-site.com/callback?sso=<base64_payload>&sig=<hmac_signature>

Decoding the base64 payload reveals the email address in plain text.

Impact

  1. Browser history: Email is recorded in browser history
  2. Nginx logs: The full URL is logged in nginx access logs
  3. Target site logs: The relying party receives the email without explicit user consent
  4. User expectation: Users typically authorize to prove “I am a legitimate user” - they don’t expect their email to be shared

Expected behavior

Users should be able to control whether their email is shared with the relying party. There should be a configuration option similar to discourse_connect_overrides_groups, discourse_connect_overrides_avatar, etc.

Currently there is no site setting to disable this behavior. Writing a plugin to override this behavior is possible but not ideal.

Reproduction steps

  1. Enable enable_discourse_connect_provider
  2. Configure discourse_connect_provider_secrets (e.g., *.example.com|secret123)
  3. Have a user authenticate via DiscourseConnect Provider
  4. Check the 302 redirect URL - email is visible in the URL parameters

The redirect URL looks like:
https://relying-party.com/sso?sso=bm9uY2U9xxx&sig=xxx

Decoding sso parameter reveals:
nonce=xxx&return_sso_url=xxx&email=user@example.com&external_id=123

Environment

  • Discourse version: (latest)
  • Self-hosted

Possible fix suggestions

  1. Add a site setting like discourse_connect_provider_includes_email (default: true for backward compatibility) to control whether email is included in the response
  2. Or implement POST-based callback instead of GET 302 redirect to avoid URL logging

Isn’t that also encrypted with the shared secret?

1 like

Hi Falco,

The HMAC signature only ensures the payload hasn’t been tampered with - it doesn’t encrypt the data. Base64 is encoding, not encryption, so anyone can decode it to read the contents.

The core issue is: we don’t want to expose unnecessary user information (like email) to “third-party sites using Discourse for SSO login”. Users authorize only to prove “I am a legitimate user” - they don’t expect their email to be shared.

Would a site setting to control whether email is returned be acceptable?

I’m curious, what is this downstream third party that has implemented our custom SSO protocol?

I’d say that is pr-welcome as long as it is not the default, so we don’t break existing sites.

Thanks for the feedback!

To give you some context on the use case: our forum is a “campus forum,” and a student built a free website for other students to use. They want to use our Discourse-based campus forum to authenticate whether a user is indeed a student at our school - essentially proving “I am a legitimate student here.”

In this scenario, the external website only needs to know “this is a valid user from our campus forum” - they don’t necessarily need the user’s email, which is sensitive information that shouldn’t be shared unnecessarily.

I’ll work on a PR that adds a site setting to control this behavior, with a default that maintains backward compatibility for existing installations.

1 like