Adding Externally-Authenticated Users to BuddyPress

BuddyPress does a great job integrating new users who register through your WordPress site into the community — optionally synchronizing profile information between BP and WP, and creating an activity feed item for new user signups.

But what about users who login against an external authentication provider, and whose accounts are created and managed by such a system? Today we’re going to look at how to bring those users into the community in the same way as those who registered locally. Be warned — this is a fairly dense piece, especially if you haven’t worked with an authenticate filter before.

A crash course on authenticate

If you are using (or writing) an external authentication plugin, you’ll need a function hooked to the authenticate filter. This function is where your plugin gets a chance to authenticate users against your outside system. It is also where your users will have subordinate WordPress accounts made and managed by the external system.

A very quick example:

function my_func_to_authenticate( $user, $username, $password ) {
 // if $user is a valid WP_User, authenticated by an upstream filter
 // pass these params to the external auth source for verification
 // proceed if user is logged in, otherwise return a WP_Error
}
add_filter( 'authenticate', 'my_func_to_authenticate', 10, 3 );

The first login

The first time your users log in to your WordPress install using their external credentials, you’ll probably want to create a WP account for them. You may skip this if you’re just trying to restrict read access, but this is about BuddyPress, so I’m assuming you want them to post and contribute to the site. :)

Here’s a rundown of the process:

  1. Suppress the BuddyPress activation email
  2. Create WP user account
  3. Update user meta and, optionally, xprofile data
  4. Activate user and set proper display_name
  5. Publish activity stream message about new user, with properly-formatted display name

Suppress the BuddyPress activation email

These users are already verified by virtue of being in our external auth system, so an activation email would just be confusing.

/** Suppress activation key email for users logging in with external auth */
add_filter( 'bp_core_signup_send_activation_key', create_function('','return false;') );

Create WordPress user account

Normally, you’d use wp_create_user (or wp_insert_user if you want access to the full set of user properties).
But BuddyPress comes with a wrapper function that can be used to do a lot of the heavy lifting for us.

$new_user = bp_core_signup_user(
	$user_login,
	$user_password,
	$user_email,
	array()
);
 
if( is_wp_error( $new_user ) ) {
	return $new_user;
}

Update user meta and, optionally, xprofile data

There are actually a few ways to handle this, ranging from bull-in-a-china-shop overwriting to graceful but more complicated filtering. Which one you choose may depend on your comfort with WP or PHP, which BuddyPress features you have enabled, or the structure of your authentication script.

Easiest

Easiest
$display_name = $first_name . ' ' . $last_name;
 
update_user_meta( $new_user, 'nickname',   $display_name  );
update_user_meta( $new_user, 'first_name', $first_name );
update_user_meta( $new_user, 'last_name',  $last_name  );
 
if( bp_is_active( 'xprofile' ) )
	xprofile_set_field_data( bp_xprofile_fullname_field_name(), $new_user, $display_name );
 
// Even though setting the xprofile value, above, should take care of this, 
// testing revealed that we needed to prime the cache with the right value
wp_cache_set( 'bp_user_fullname_' . $new_user, $display_name, 'bp' );

PROS:

  • Very straightforward and procedural — no objects or data sharing needed
  • Complete control over format of display_name and BP fullname field
  • Control over any other BP xprofile values you want to set
  • Does not require xprofile or WP-BP profile sync in order to operate

CONS:

  • Will ultimately drift out of sync with BP development
  • Still need to set the display_name property below
  • Might as well use wp_insert_user, since we’re manually adding so much

Shortest

Shortest

Replace the bp_core_signup_user call above with this:

$new_user = bp_core_signup_user(
	$login_name,
	$password,
	$email,
	array(
		'profile_field_ids' => bp_xprofile_fullname_field_name(),
		"field_{bp_xprofile_fullname_field_name()}" => $display_name
	)
);

PROS:

  • Uses BP to eliminate the need for additional filters or processing
  • Least code redundancy of all
  • Can supply additional xprofile fields to be added to the new user
  • Most natural method in terms of program flow

CONS:

  • Complex meta parameter structure can be difficult to read
  • Parameter or data structure may change in future BP releases
  • Requires xprofile and WP-BP profile sync to update WP display_name and usermeta

Fanciest

Fanciest
function my_display_name_filter( $user_id, $user_login, $user_password, $user_email, $usermeta ) {
	// must get $display_name from another part of the authentication process
	if ( bp_is_active( 'xprofile' ) ) {
		xprofile_set_field_data( bp_xprofile_fullname_field_name(), $new_user, $display_name );
	}
}

Change your authenticate function to look like this:

function my_func_to_authenticate( $user, $username, $password ) {
 
	// ... authenticate user
	add_action( 'bp_core_signup_user', 'my_display_name_filter', 9, 5 );
	$new_user = bp_core_signup_user(
		$login_name,
		$password,
		$email,
		array()
	);
}

PROS:

  • Uses BP filters for forward compatibility
  • Reduces the amount of repeated code

CONS:

  • Requires an object or data-sharing infrastructure to get user details to the filter
  • Requires xprofile and WP-BP profile sync to update WP display_name and usermeta


Now that the xprofile and usermeta data are set up, it’s time to activate the user! This is done by simply setting the user_status column to 0.

Activate user (and set proper display_name, if needed)

Update 08-06-2012: I used to recommend wp_update_user for this, but that has proven unreliable on a recent project. So to be safe, just use a direct query.

// activate user and set display name appropriately
$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->users SET display_name=%s, user_status=0 WHERE ID=%d", $display_name, $new_user ) );

And if you set $display_name above, use the shortened version:

// activate user
$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->users SET user_status=0 WHERE ID=%d", $new_user ) );

Publish an activity stream message about the new user and notify the site admin

Since the display_name property and associated xprofile field are finally set the way we want them, it’s time to trigger the activity stream message and administrator notification.

bp_core_new_user_activity( $new_user );
wp_new_user_notification( $new_user );

And you’re done!

Return a WP_User object to let other authentication filters know you’ve handled this user.

return new WP_User( $new_user );

A complete example

function my_func_to_authenticate( $user, $username, $password ) {
	// if $user is a valid WP_User, authenticated by an upstream filter
	if( is_a( $user, 'WP_User' ) ) return $user;
 
	// pass these params to the external auth source for verification
	// proceed if user is logged in, otherwise return a WP_Error
	if( $user_details = my_external_auth( $username, $password ) ) {
 
		// for simplicity's sake, I'm assuming that $user_details is an array with all the right keys
		// in practice, more checks would be required
		extract( $user_details );
 
		// this function returns the new user account ID if successful
		$new_user = bp_core_signup_user(
			$username,
			$password,
			$user_email,
			array()
		);
 
		// If signup returned an error, skip the rest
		if( is_wp_error( $new_user ) ) {
			return $new_user;
		}
 
		$display_name = $first_name . ' ' . $last_name;
 
		update_user_meta( $new_user, 'nickname',   $display_name  );
		update_user_meta( $new_user, 'first_name', $first_name );
		update_user_meta( $new_user, 'last_name',  $last_name  );
 
		if( bp_is_active( 'xprofile' ) )
			xprofile_set_field_data( bp_xprofile_fullname_field_name(), $new_user, $display_name );
 
		// Even though setting the xprofile value, above, should take care of this, 
		// testing revealed that we needed to prime the cache with the right value
		wp_cache_set( 'bp_user_fullname_' . $new_user, $display_name, 'bp' );
 
		// activate user and set display name appropriately
		$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->users SET display_name=%s, user_status=0 WHERE ID=%d", $display_name, $new_user ) );
 
		bp_core_new_user_activity( $new_user );
		wp_new_user_notification( $new_user );
 
		return new WP_User( $new_user );
	}
	// you can return FALSE, or a WP_Error if you have something specific to say
	return new WP_Error( 'login_failed', __( 'Could not authenticate user' ) );
}
add_filter( 'authenticate', 'my_func_to_authenticate', 10, 3 );

If you have questions, or if you put this to use in your next project, feel free to drop me a line in the comments!

2 Responses

  1. stijndewitt says:

    Thanks for this great post! It was exactly what I am looking for.

    I have some questions though. I see that you perform an update statement on the database:

    // activate user and set display name appropriately
    $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->users SET display_name=%s, user_status=0 WHERE ID=%d", $display_name, $new_user ) );

    1. Is this necessary? I was hoping to avoid storing a 'shadow' record in WordPress as the user is already stored in the external system.
    2. It looks like you are registering a new user (and activating it etc)… How does this work? A new user is registered for every login?

    Thanks for the great article and keep on writing them!

    -Stijn

    • ddean says:

      Hi Stijn,

      This technique does create a “shadow” WP user for the external login. A lot of BuddyPress uses the WP user ID extensively, so it would take a lot more work to let external users participate in a BuddyPress site without a local user ID.

      Your second question is related to the first one. Because we\’re creating a local user account and hooking `authenticate`, this code is only called if the user fails to log in locally but successfully logs in to the external auth system.

      Thanks for the comment!

      – David

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>