Skip to content

Instantly share code, notes, and snippets.

@kasparsd
Created September 25, 2024 15:01
Show Gist options
  • Save kasparsd/4560f3013afccbaa327beb4dc90eb477 to your computer and use it in GitHub Desktop.
Save kasparsd/4560f3013afccbaa327beb4dc90eb477 to your computer and use it in GitHub Desktop.
A prototype of native WP multisite single sign-on (SSO)
<?php
/**
* Plugin Name: Multisite SSO Login
* Network: true
*
* @see https://x.com/konstruktors/status/1838956536049094659
*/
if ( ! is_multisite() ) {
return;
}
function is_sso_site() {
return is_main_site();
}
function get_sso_login_url( $query_args = [] ) {
$query_args = wp_parse_args(
$query_args,
[
'multisite_sso_blog_id' => get_current_blog_id(),
]
);
return add_query_arg( $query_args, network_site_url( 'wp-login.php', 'login' ) );
}
function get_sso_callback_url( $blog_id, $query_args = [] ) {
return add_query_arg( $query_args, get_site_url( $blog_id, 'wp-login.php', 'login' ) );
}
add_filter(
'login_message',
function ( $messages ) {
if ( is_user_logged_in() || is_sso_site() ) {
return;
}
$sso_args = [];
$redirect_to = filter_input( INPUT_GET, 'redirect_to', FILTER_SANITIZE_URL );
if ( $redirect_to ) {
$sso_args['redirect_to'] = rawurlencode( $redirect_to );
}
$messages .= '<style type="text/css">
.login .wp-multisite-sso-login--cta a { font-size:inherit; float:none; display:inline-block; width:100%; clear:both; text-align:center; }
</style>';
$messages .= sprintf(
'<div class="message wp-multisite-sso-login--cta">
<p><a href="%s" class="button button-primary button-large">%s</a></p>
</div>',
esc_url( get_sso_login_url( $sso_args ) ),
esc_html__( 'Login with Multisite SSO' ),
);
return $messages;
},
200
);
add_action(
'login_init',
function () {
if ( ! is_sso_site() || is_user_logged_in() ) {
return;
}
$blog_id = isset( $_REQUEST['multisite_sso_blog_id'] ) ? absint( $_REQUEST['multisite_sso_blog_id'] ) : null;
if ( empty( $blog_id ) ) {
return;
}
$blog = get_blog_details( $blog_id );
if ( ! $blog ) {
return;
}
add_action(
'login_form',
function () use ( $blog_id ) {
?>
<input type="hidden" name="multisite_sso_blog_id" value="<?php echo esc_attr( $blog_id ); ?>">
<?php
}
);
add_filter(
'wp_login_errors',
function ( $errors ) use ( $blog ) {
$message = sprintf(
esc_html__( 'You are logging into %s' ),
sprintf(
'<a href="%s" target="_blank">%s</a>',
esc_url( $blog->home ),
esc_html( $blog->blogname )
)
);
$errors->add( 'multisite_sso_notice', $message, 'message' );
return $errors;
}
);
}
);
add_filter(
'login_redirect',
function ( $redirect_to, $requested_redirect_to, $user ) {
if ( ! is_sso_site() ) {
return $redirect_to;
}
$blog_id = isset( $_REQUEST['multisite_sso_blog_id'] ) ? absint( $_REQUEST['multisite_sso_blog_id'] ) : null;
if ( ! empty( $blog_id ) && $user instanceof WP_User ) {
// Add the destination blog hostname to the allowed safe-redirect list.
add_filter(
'allowed_redirect_hosts',
function ( $hosts ) use ( $blog_id ) {
$hosts[] = wp_parse_url( get_site_url( $blog_id ), PHP_URL_HOST );
$hosts[] = wp_parse_url( get_home_url( $blog_id ), PHP_URL_HOST );
return $hosts;
}
);
$session_token = wp_generate_password( 42, false, false );
// Relay the private session token through a back-channel.
update_user_meta( $user->ID, 'multisite_sso_token', $session_token );
$args = [
'multisite_sso_user_id' => $user->ID,
'multisite_sso_token' => wp_hash( $session_token ), // Transfer hashed for one-time verification.
];
if ( ! empty( $requested_redirect_to ) && $redirect_to !== $requested_redirect_to ) {
$args['redirect_to'] = urlencode( $requested_redirect_to );
}
return get_sso_callback_url( $blog_id, $args );
}
return $redirect_to;
},
100,
3
);
add_action(
'login_init',
function () {
if ( is_sso_site() || is_user_logged_in() ) {
return;
}
$login_user_id = isset( $_GET['multisite_sso_user_id'] ) ? absint( $_GET['multisite_sso_user_id'] ) : null;
$login_token_hash = isset( $_GET['multisite_sso_token'] ) ? sanitize_text_field( $_GET['multisite_sso_token'] ) : null;
if ( ! $login_user_id || ! $login_token_hash ) {
return;
}
// Get the session token from the SSO source and delete it to ensure it is used only once.
$token_source = get_user_meta( $login_user_id, 'multisite_sso_token', true );
delete_user_meta( $login_user_id, 'multisite_sso_token' );
if ( $token_source && hash_equals( wp_hash( $token_source ), $login_token_hash ) ) {
wp_clear_auth_cookie();
wp_set_auth_cookie( $login_user_id );
wp_set_current_user( $login_user_id );
$redirect_to = filter_input( INPUT_GET, 'redirect_to', FILTER_SANITIZE_URL );
if ( ! $redirect_to ) {
$redirect_to = get_dashboard_url( $login_user_id );
}
wp_redirect( $redirect_to );
exit;
}
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment