创建 DiscourseConnect 登录链接

:bookmark: This documentation explains how to create links on a DiscourseConnect provider site that log users into Discourse and redirect them to a specific Discourse URL.

:person_raising_hand: Required user level: Administrator

Sites using DiscourseConnect can add links on their DiscourseConnect provider site that will log users into Discourse and redirect them to a specific Discourse URL. This is done by creating links that point to the /session/sso route that have a return_path parameter set to the path of the Discourse page you want users to end up on.

The link’s href property should be in the form below, with the path you want users to end up on substituted for <relative_path>:

https://forum.example.com/session/sso?return_path=<relative_path>

An example anchor tag that will log in a user and redirect them to a Discourse site’s homepage:

<a href="https://forum.example.com/session/sso?return_path=/">Community</a>

An example anchor tag that will log in a user and redirect them to the Top Topics page:

<a href="https://forum.example.com/session/sso?return_path=/top">Top Topics</a>

How the return_path is stored on Discourse

Discourse stores the value of the return_path parameter on the session object that is created when a user visits the /session/sso route. At the end of the DiscourseConnect authentication process, Discourse redirects users to the return_path.

Making the process seamless for authenticated users

When a user visits the Discourse /session/sso route, they are redirected to the URL set by the discourse connect url site setting. The DiscourseConnect provider will then handle the authentication process in the same way as it would if the user had clicked the Discourse Login button.

For the authentication process to be seamless for users who are already logged in on the authentication provider site, the authentication provider’s DiscourseConnect code needs to check to see if the user is logged in or not. If the user isn’t logged in, take them through the auth providers login process. If the user is already logged in, skip the login process on the auth provider site.

Here’s a commented example, using code from the WP Discourse plugin. It demonstrates how authenticated users can be handled differently than unauthenticated users:

public function sso_parse_request( $wp ) {
    // Check if Single Sign-On (SSO) is enabled in the plugin options
    if ( empty( $this->options['enable-sso'] ) ) {
        return null;
    }

    // Handle any logout requests before proceeding with SSO
    $this->handle_logout_request();

    // Check if the 'sso' and 'sig' parameters exist in the query variables
    if ( array_key_exists( 'sso', $wp->query_vars ) && array_key_exists( 'sig', $wp->query_vars ) ) {
        // Sanitize the 'sso' payload and signature to ensure they are safe for use
        $payload = sanitize_text_field( $wp->query_vars['sso'] );
        $sig     = sanitize_text_field( $wp->query_vars['sig'] );

        // If the user is not logged in to WordPress, redirect to the login page
        // This ensures that users without an active session are prompted to log in to WordPress first
        if ( ! is_user_logged_in() ) {
            // Construct a URL to redirect back to after logging in
            $redirect = add_query_arg( $payload, $sig );
            // Generate the WordPress login URL with the redirect parameter
            $login    = wp_login_url( esc_url_raw( $redirect ) );

            // Trigger an action before the login redirection (optional for logging or custom actions)
            do_action( 'wpdc_sso_before_login_redirect', $redirect, $login );

            // Redirect to the WordPress login page
            return $this->redirect_to( $login );
        } else {
            // If the user is already authenticated in WordPress, bypass the login process
            // and proceed with validating the SSO payload and signature.
            $sso_secret = $this->options['sso-secret'];
            $sso        = new SSO( $sso_secret );
            
            // Validate the payload and signature using the SSO secret
            if ( ! ( $sso->validate( $payload, $sig ) ) ) {
                // Handle invalid SSO requests
                return $this->handle_error( 'parse_request.invalid_sso' );
            }

            // Get the current logged-in WordPress user
            $current_user = wp_get_current_user();
            // Prepare SSO parameters using the logged-in user's data
            $params       = $this->get_sso_params( $current_user );

            try {
                // Generate a nonce from the payload and build the SSO login string
                $params['nonce'] = $sso->get_nonce( $payload );
                $q               = $sso->build_login_string( $params );
            } catch ( \Exception $e ) {
                // Handle exceptions if there is an issue with SSO parameter generation
                return $this->handle_error( 'parse_request.invalid_sso_params', array( 'message' => esc_html( $e->getMessage() ) ) );
            }

            // Trigger an action before redirecting the user for SSO login (useful for logging)
            do_action( 'wpdc_sso_provider_before_sso_redirect', $current_user->ID, $current_user );

            // Log the SSO success if verbose logging is enabled
            if ( ! empty( $this->options['verbose-sso-logs'] ) ) {
                $this->logger->info( 'parse_request.success', array( 'user_id' => $current_user->ID ) );
            }

            // Redirect the authenticated user to the DiscourseConnect login URL with the SSO login string
            return $this->redirect_to( $this->options['url'] . '/session/sso_login?' . $q );
        }
    }

    // Return null if no SSO parameters are found in the request
    return null;
}

Setting the return path to non-Discourse URLs

Discourse allows you to login a user and redirect them to a non-Discourse URL. Note that for this to work you need to add the URL to the discourse connect allowed redirect domains site setting. By default this setting is blank - preventing redirects to non-Discourse URLs. If you enable it, be sure to use the absolute URL in the return_path parameter for any non-Discourse URLs that you want to direct users to.

Last edited by @simon 2024-09-25T07:22:09Z

Check documentPerform check on document:
20 个赞

我正在使用此方法,但每次都需要通过 sso 登录。

我们如何实现无缝登录,即如果我已经登录到 discourse,就不需要再次访问 sso 页面进行登录?

1 个赞

除非有什么变化,否则链接就是这样工作的。例如,如果您已登录 Discourse 并单击 SSO 提供商网站上指向 https://forum.example.com/session/sso?return_path=/t/some-slug/23 的链接,您应该会被无缝重定向到 /t/some-slug/23,而无需先访问登录页面。

1 个赞

我已经更新到最新的 discourse 版本,SSO 对我来说是这样工作的:

正如你所见,我已经登录了,但是当我输入像 https://forum.example.com/session/sso?return_path=/t/some-slug/23 这样的 URL 时,我会被再次重定向到 SSO 登录页面。

我认为发生的情况是,当您访问类似 https://forum.example.com/session/sso?return_path=/t/some-slug/23 这样的路由时,Discourse 会将您重定向到 discourse connect url,无论您是否已登录 Discourse。这发生在这里:

然后,SSO 提供商网站需要处理已登录网站的用户的情况。WP Discourse 插件是这样处理的:

该代码(else 语句后面的部分)处理已登录 WordPress 的用户的情况。他们将被重定向回由 return_path 查询参数提供的 URL。因此,从用户的角度来看,他们被直接带到 return path URL,但实际发生的是他们被重定向到 SSO 提供商网站,然后再返回 Discourse。

我认为您网站上的问题在于您的 SSO 代码没有处理用户已登录网站的情况。

我现在没有设置来测试这一点。我可能错误地解读了代码。在查看代码之前,我认为 Discourse 端会运行一个检查,以查看用户是否已登录 Discourse,但这似乎不是它的工作方式。

3 个赞

非常感谢您的解释。

是的,这是我们将在 SSO 中修复的一个问题。

但是

我认为在 Discourse 端进行此检查会带来更好的用户体验。

1 个赞

我对 OP 进行了编辑,添加了关于该流程如何工作以及如何处理已在 SSO 提供商网站上进行身份验证的用户的信息。这是一个经常被问到的问题。

也许这个主题的一个不太详细的版本可以整合到 Setup DiscourseConnect - Official Single-Sign-On for Discourse (sso) - #482 中。该主题没有提到 return_path 查询参数。