Jump to content

Community

Developer Documentation

Creating a Button Handler

Note: If you are creating an OAuth-based Handler, see Creating an OAuth-Based Handler. Only use this documentation for non-OAuth-based Handlers.

To make a login handler where the user clicks a button (that is not OAuth-based) you will add the \IPS\Login\Handler\ButtonHandler trait and implement the methods it requires:

  • buttonColor(), buttonIcon(), buttonText() and buttonClass() control how the button looks.
  • authenticateButton() handles what happens when the button is clicked. You are responsible for whatever mechanism you want to use to then authenticate the user (this will almost always involve redirecting to an external service). You will then return an \IPS\Member object (including creating the account if there isn't one already and ensuring reauthentication happens if there is already an account but the user hasn't logged in with your handler before).

This is a basic skeleton of a button based Login Handler. This code will process a login using a hardcoded username and email address. Obviously a real login handler wouldn't hardcode this data but would look it up in a database or through an external service.

class _MyLoginHandler extends \IPS\Login\Handler
{
	use \IPS\Login\Handler\ButtonHandler;
	
	/**
	 * @brief	Can we have multiple instances of this handler?
	 */
	public static $allowMultiple = FALSE;
		
	/**
	 * Get title
	 *
	 * @return	string
	 */
	public static function getTitle()
	{
		return 'my_cusom_login_handler'; // Create a langauge string for this
	}
	
	/**
	 * Authenticate
	 *
	 * @param	\IPS\Login	$login				The login object
	 * @return	\IPS\Member
	 * @throws	\IPS\Login\Exception
	 */
	public function authenticateButton( \IPS\Login $login )
	{
		/* If we haven't been redirected back, redirect the user to external site */
		if ( !isset( \IPS\Request::i()->success ) )
		{
			$urlToRedirectBackTo = $destination = $login->url->setQueryString( array(
				'_processLogin'	=> $this->id,
				'csrfKey'		=> \IPS\Session::i()->csrfKey,
				'ref'			=> \IPS\Request::i()->ref,
			) );
			
			// NOTE: A real example would actually redirect to an external site which would authenticate
			// the user and then redirect back to $urlToRedirectBackTo. But since this is just an example
			// we'll redirect straight to it
			\IPS\Output::i()->redirect( $urlToRedirectBackTo->setQueryString( 'success', 1 ) );
		}
		
		/* Get the user data. NOTE: Obviously a real example would look this up in some kind of database or external service or it would have been returned as the user was redirected back */
		$userId = 1; // NOTE: This would be set to some kind of identifier for the user within that service. It doesn't have to be numeric, but does have to be unique.
		$name = 'example'; // NOTE: We will use this later to create an account if it doesn't exist. If your login handler doesn't store display names, set this to NULL (and the user will be asked to provide one)
		$email = 'example@example.com'; // NOTE: We will use this later to create an account if it doesn't exist. If your login handler doesn't store display names, set this to NULL (and the user will be asked to provide one)
		
		/* Find their local account if they have already logged in using this method in the past */
		try
		{
			$link = \IPS\Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $userId ) )->first();
			$member = \IPS\Member::load( $link['token_member'] );
			
			/* If the user never finished the linking process, or the account has been deleted, discard this access token */
			if ( !$link['token_linked'] or !$member->member_id )
			{
				\IPS\Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $link['token_member'] ) );
				throw new \UnderflowException;
			}
			
			/* ... and return the member object */
			return $member;
		}
		catch ( \UnderflowException $e ) { }
				
		/* Otherwise, we need to either create one or link it to an existing one */
		try
		{
			/* If the user is setting this up in the User CP, they are already logged in. Ask them to reauthenticate to link those accounts */
			if ( $login->type === \IPS\Login::LOGIN_UCP )
			{
				$exception = new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT );
				$exception->handler = $this;
				$exception->member = $login->reauthenticateAs;
				throw $exception;
			}
			
			/* Try to create one. NOTE: Invision Community will automatically throw an exception which we catch below if $email matches an existing account, if registration is disabled, or if Spam Defense blocks the account creation */			
			$member = $this->createAccount( $name, $email );
			
			/* If we're still here, a new account was created. Store something in core_login_links so that the next time this user logs in, we know they've used this method before */
			\IPS\Db::i()->insert( 'core_login_links', array(
				'token_login_method'	=> $this->id,
				'token_member'			=> $member->member_id,
				'token_identifier'		=> $userId,
				'token_linked'			=> 1,
			) );
			
			/* Log something in their history so we know that this login handler created their account */
			$member->logHistory( 'core', 'social_account', array(
				'service'		=> static::getTitle(),
				'handler'		=> $this->id,
				'account_id'	=> $userId,
				'account_name'	=> $name,
				'linked'		=> TRUE,
				'registered'	=> TRUE
			) );
			
			/* Set up syncing options. NOTE: See later steps of the documentation for more details - it is fine to just copy and paste this code */
			if ( $syncOptions = $this->syncOptions( $member, TRUE ) )
			{
				$profileSync = array();
				foreach ( $syncOptions as $option )
				{
					$profileSync[ $option ] = array( 'handler' => $this->id, 'ref' => NULL, 'error' => NULL );
				}
				$member->profilesync = $profileSync;
				$member->save();
			}
			
			return $member;
		}
		catch ( \IPS\Login\Exception $exception )
		{
			/* If the account creation was rejected because there is already an account with a matching email address
				make a note of it in core_login_links so that after the user reauthenticates they can be set as being
				allowed to use this login handler in future */
			if ( $exception->getCode() === \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT )
			{
				\IPS\Db::i()->insert( 'core_login_links', array(
					'token_login_method'	=> $this->id,
					'token_member'			=> $exception->member->member_id,
					'token_identifier'		=> $userId,
					'token_linked'			=> 0,
				) );
			}
			
			throw $exception;
		}		
	}
	
	/**
	 * Get the button color
	 *
	 * @return	string
	 */
	public function buttonColor()
	{
		return '#ff3399';
	}
	
	/**
	 * Get the button icon
	 *
	 * @return	string
	 */
	public function buttonIcon()
	{
		return 'sign-in'; // A fontawesome icon
	}
	
	/**
	 * Get button text
	 *
	 * @return	string
	 */
	public function buttonText()
	{
		return 'sign_in_with_my_cusom_login_handler'; // Create a language string for this
	}

	/**
	 * Get button CSS class
	 *
	 * @return	string
	 */
	public function buttonClass()
	{
		return '';
	}

	
}

 

Edited by Mark

  Report Document


×
×
  • Create New...