Bypass the account creation modal when using OAuth2

When logging in via OAuth2 (custom server) for the first time, the user is welcomed by this account creation modal.

Why popping it if all the infos are valid? Why not create the account automatically?

Is there any way to bypass it?

In my case, it adds confusion to the user because he’s supposed to have a single account (the OAuth2 one) for several apps. Nevertheless, I think it it should pop if the infos are invalid (conflict or such).

The other solution for me would be to create the user via the API beforehand (when its OAuth2 account is created), then the window doesn’t appear at first login, but it forces me to enable local logins and I’d prefer not to.

You really want SSO. That way the other server controls everything. With oauth the user can still choose a username.

Take a look at this Disable "create account" screen for CAS logins . You should be able to do something similar. I have no idea why this is not a default feature.

4 Likes

We do exactly this in the official SAML plugin.

The reason we show the modal is so that people can change their Name/Username if the “Social login provider” doesn’t have the best information available. For things like facebook/twitter this makes a lot of sense, but it makes less sense when corporate SAML/OAuth/OIDC is being used.

I think a core site setting for “Automatically create accounts” would be useful. But we would need to think about whether it should work when registration is disabled.

8 Likes

I modified discourse-oauth2-plugin to create the user account in the after_authenticate hook but the modal still pops and the suggested username is a variation of the OAuth2 username (because, eh, it’s already taken at this point).

I’m not familiar enough with Discourse (and Rails) to dig this, so if anyone could tell me if there is an “easy” way to dismiss this modal, other than forking discourse/discourse if possible…

SSO would make me implement another auth endpoint on my OAuth2 server and I’d prefer not to, to keep consistency with the other apps using OAuth2 in the stack.

1 Like

The ugly workaround I found for the time being:

  • Enable local logins

  • Create users beforehand via the API

  • Create a new Component in the theme

    • CSS
    .sign-up-button { display: none; }
    .modal-backdrop, .login-modal * { visibility: hidden; }
    
    • HTML
    <script>
    // Minified https://cdnjs.com/libraries/arrive
    var Arrive=function(e,t,n){"use stric...
    </script>
    
    <script>
        $(document).ready(function() {
            $(document).arrive('.login-modal', function() {
                $('.login-modal button.oauth2_basic').click();
            });
        });
    </script>
    

In a nutshell, I allow local logins but hides the sign up button, and when the log in modal shows (wether it’s coming from a click on the log in button, the reply button…) the modal is hidden and it simulates a click on the OAuth2 login method.

You can throw stones at me until I figure out a cleaner, lower-level way to do this :slight_smile:

1 Like

Would you mind sharing your code?

1 Like

Added code is between the comments (taken from Disable "create account" screen for CAS logins).

  def after_authenticate(auth)
    log("after_authenticate response: \n\ncreds: #{auth['credentials'].to_hash}\ninfo: #{auth['info'].to_hash}\nextra: #{auth['extra'].to_hash}")

    result = Auth::Result.new
    token = auth['credentials']['token']
    user_details = fetch_user_details(token, auth['info'][:id])

    result.name = user_details[:name]
    result.username = user_details[:username]
    result.email = user_details[:email]
    result.email_valid = result.email.present? && SiteSetting.oauth2_email_verified?
    avatar_url = user_details[:avatar]

    current_info = ::PluginStore.get("oauth2_basic", "oauth2_basic_user_#{user_details[:user_id]}")
    if current_info
      result.user = User.where(id: current_info[:user_id]).first
    elsif SiteSetting.oauth2_email_verified?
      result.user = User.find_by_email(result.email)
      if result.user && user_details[:user_id]
        ::PluginStore.set("oauth2_basic", "oauth2_basic_user_#{user_details[:user_id]}", user_id: result.user.id)
      end
    end

    download_avatar(result.user, avatar_url)

    result.extra_data = { oauth2_basic_user_id: user_details[:user_id], avatar_url: avatar_url }

    # --------------
    if User.find_by_email(user_details[:email]).nil?
      user = User.create(name: user_details[:name], email: user_details[:email], username: user_details[:username])
      log("created user account")
    end
    # --------------

    result
  end

Try moving that code directly underneath

elsif SiteSetting.oauth2_email_verified?

and change user to result.user

  def after_authenticate(auth)
    log("after_authenticate response: \n\ncreds: #{auth['credentials'].to_hash}\ninfo: #{auth['info'].to_hash}\nextra: #{auth['extra'].to_hash}")

    result = Auth::Result.new
    token = auth['credentials']['token']
    user_details = fetch_user_details(token, auth['info'][:id])

    result.name = user_details[:name]
    result.username = user_details[:username]
    result.email = user_details[:email]
    result.email_valid = result.email.present? && SiteSetting.oauth2_email_verified?
    avatar_url = user_details[:avatar]

    current_info = ::PluginStore.get("oauth2_basic", "oauth2_basic_user_#{user_details[:user_id]}")
    if current_info
      result.user = User.where(id: current_info[:user_id]).first
    elsif SiteSetting.oauth2_email_verified?
      # --------------
      if User.find_by_email(user_details[:email]).nil?
        result.user = User.create(name: user_details[:name], email: user_details[:email], username: user_details[:username])
        log("created user account")
      end
      # --------------

      result.user = User.find_by_email(result.email)
      if result.user && user_details[:user_id]
        ::PluginStore.set("oauth2_basic", "oauth2_basic_user_#{user_details[:user_id]}", user_id: result.user.id)
      end
    end

    download_avatar(result.user, avatar_url)

    result.extra_data = { oauth2_basic_user_id: user_details[:user_id], avatar_url: avatar_url }

    result
  end

Note that modifying code in this way isn’t great, because it will break if we update the method later. This will almost certainly require maintenance in the future.

3 Likes

Thanks @david it works with your version.

Nevertheless, I’m now welcomed by this modal (button label redacted)

oauth2 email verified setting is enabled.
Clicking again logs me in, but this is still an extra step for the user.

This method is in my discourse-oauth2-plugin fork, shouldn’t it stay stable as long as I don’t update the major version?

Update your User.create line to include active: true

In theory, yes

1 Like

Err I could have guessed it (and I actually do it with the API client), thanks :slight_smile:

2 Likes