Create a DiscourseConnect login link

: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 in the server session, keyed by the SSO nonce that is generated when a user visits the /session/sso route. At the end of the DiscourseConnect authentication process, Discourse looks up the return_path using the nonce and redirects users to that 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 domain to the discourse connect allowed redirect domains site setting. By default this setting is blank - preventing redirects to non-Discourse URLs. You can also use * as a wildcard to allow all domains. 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:
21개의 좋아요

이 방법을 사용하고 있지만, 매번 SSO를 통해 로그인해야 합니다.

이미 Discourse에 로그인한 상태라면, SSO 페이지로 가서 다시 로그인할 필요가 없도록 어떻게 하면 좋을까요?

1개의 좋아요

특별히 변경된 사항이 없다면, 해당 링크는 그렇게 동작하도록 설계되어 있습니다. 예를 들어, 디스코urs에 로그인된 상태에서 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로 리다이렉트되는 것 같습니다. 이 동작은 다음 코드에서 발생합니다:

이후 SSO 제공 사이트에서는 이미 해당 사이트에 로그인된 사용자의 경우를 처리할 것으로 기대됩니다. WP Discourse 플러그인이 이를 처리하는 방식은 다음과 같습니다:

해당 코드(else 문 이후 부분)는 이미 WordPress에 로그인된 사용자의 경우를 처리합니다. 사용자는 return_path 쿼리 파라미터로 전달된 URL로 다시 리다이렉트됩니다. 따라서 사용자의 관점에서는 반환 경로 URL로 직접 이동하는 것처럼 보이지만, 실제로는 SSO 제공 사이트로 리다이렉트된 후 다시 Discourse로 돌아오는 과정이 이루어집니다.

제 생각에는 여러분 사이트의 문제가 SSO 코드가 이미 사이트에 로그인된 사용자의 경우를 처리하지 않고 있기 때문일 수 있습니다.

현재 저는 이를 테스트할 수 있는 환경이 갖춰져 있지 않습니다. 코드를 잘못 읽었을 수도 있습니다. 코드를 보기 전에는 Discourse 측에서 사용자가 이미 로그인되어 있는지 확인하는 체크가 수행된다고 생각했지만, 실제로는 그렇게 작동하지 않는 것 같습니다.

3개의 좋아요

설명해 주셔서 정말 감사합니다.

네, 맞습니다. 저희 SSO에서 수정할 예정입니다.

하지만

이 확인을 Discourse 측에서 수행했다면 더 나은 사용자 경험을 제공했을 것이라 생각합니다.

1개의 좋아요

OP를 편집하여 프로세스가 어떻게 작동하는지, 그리고 SSO 제공 사이트에서 이미 인증된 사용자의 경우를 어떻게 처리하는지에 대한 세부 정보를 추가했습니다. 몇 번이나 반복적으로 제기된 질문입니다.

이 주제의 덜 상세한 버전을 https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045/482에 통합할 수도 있습니다. 해당 주제에서는 return_path 쿼리 매개변수에 대해 언급되어 있지 않습니다.