Jump to content

Developer Documentation

Developing single sign-on (SSO) integration

SSO, or Single Sign On, is a technique where-by one (or more) applications can automatically recognize a user as logged in when that user has logged in elsewhere. You can implement single sign on with IPS Community Suite by letting a remote application recognize a user who has already logged in to the IPS Community Suite as being logged in within the remote application, or by using a plugin with the IPS Community Suite to have it check for users logged in already through an external application.

 

IPS Community Suite as the Master

If you want to let external applications recognize users who are logged in to the IPS Community Suite as being logged in on those external applications, you will essentially need to load the Invision Community software and validate if the user is logged in.

Reading the IPS Community Suite session

If your external application is on the same domain or subdomain as your IPS Community Suite you can easily include the IPS Community Suite framework and use the objects and methods made available to validate a user as being logged in. The smallest code example to verify a user as being logged in is:

/* Require the init.php file from the Community Suite root directory */
require '/path/to/suite/init.php';

/* Initiate the session to verify who this user is */
\IPS\Session\Front::i();

/* Print the user's name */
print \IPS\Member::loggedIn()->name;

Here we load the init.php file located in the IPS Community Suite root directory, then we initiate the session, and finally we print the member's name.  It is also possible to load the values stored in the user's cookies (if any) and validate them against the framework directly, however for the purposes of SSO it is simpler and more accurate to simply check the user's session as per the code snippet above.

Warning

Generally, when you are checking if a user is logged in through the IPS Community Suite in an external application you should redirect registration, login, logout, change email and change password requests back to the community. This allows you to centralize all account activities.

 

IPS Community Suite as the Slave

If you want to allow users who have logged in to a third party application to be automatically logged in to the community, you will need to create a plugin. The plugin should contain a code hook that extends the class \IPS\Session\Front, and then you will want to override the read() method.

Tip

TIP: You can also create a code hook to override \IPS\Session\Admin if you have a need to implement SSO for the ACP (this is NOT recommended in most cases, as it may allow for unforeseen access to your admin control panel through your own custom code if you are not careful).

Within the read method, generally speaking you will want to first check if the user is logged in locally by calling the parent read method. This saves you from checking your remote application on every page load once the user has been authenticated. You will want to check against the local cache as the standard session handler does, and you will want to ensure you only check against a remote application if you have not already done so. One way to accomplish this is to add a database column to your core_sessions table and then only check the remote application if the column has not been set yet.

Your hook will vary based upon your needs, but a general outline is as follows:

	/**
	 * Guess if the user is logged in
	 *
	 * This is a lightweight check that does not rely on other classes. It is only intended
	 * to be used by the guest caching mechanism so that it can check if the user is logged
	 * in before other classes are initiated.
	 *
	 * This method MUST NOT be used for other purposes as it IS NOT COMPLETELY ACCURATE.
	 *
	 * @return	bool
	 */
	public static function loggedIn()
	{
		/* Check to guess if the user might be logged in. We can do this, for instance, by checking if a cookie that might indicate the user is logged
			in has been set. Otherwise, guest page caching will kick in and a user who logs in may not see they are logged in initially because a cached
			guest page is served until they refresh. */

		return parent::loggedIn();
	}

	/**
	 * Read Session
	 *
	 * @param   string  $sessionId  Session ID
	 * @return  string
	 */
	public function read( $sessionId )
	{
		/* Let normal class do its thing */
		$result = call_user_func_array( 'parent::read', func_get_args() );

		/* If we're already logged in, return */
		if( $this->member->member_id )
		{
			return $result;
		}

		/* Here we would check if the user is logged in according to the front end */
		$this->checkIfUserIsLoggedIn();

		/* If the user is logged in, we should now either create their account if it does not exist, or update their details if appropriate. For instance
			if the user's email address has changed, we should update their local account */
		$this->createOrUpdateUserAccount();

		/* If we're logged in now, set the cookies */
		if( $this->member->member_id )
		{
			/* At this point, we're logged in. Have a cookie. */
			/* Calling setMember() automatically checks that a member_login_key exists and the expiration is still valid */
			$this->setMember( $this->member );

			/* Is this a known device? */
			$device = \IPS\Member\Device::loadOrCreate( $this->member );
			$device->anonymous = 0;
			$device->updateAfterAuthentication( true, \IPS\Login\Handler::findMethod( 'IPS\Login\Handler\Standard' ) );
		}

		return $result;
	}

The above example should illustrate the basic principles of overloading our default session class, checking your remote application, and then handling the response.

Note

You should always use a globally unique user ID to match users in your external service to Invision Community. When you validate that a user is logged in, your service should return the user's ID, which you should then store in Invision Community. When later attempting to check if a user exists, you should use this ID to do so. To do this you will generally need to

  1. Add a column to core_members to store the ID, such as remote_id
  2. Add a hook on \IPS\Member like so

        /**
         * Add our own column to default fields
         */
        public function __construct()
        {
            static::$databaseIdFields = array_merge( static::$databaseIdFields, array( 'remote_id' ) );

        }
            parent::__construct();

  3. When loading the member, pass the appropriate parameter like so
    $this->member = \IPS\Member::load( $remoteIdValue, 'remote_id' );

It may be tempting to try to just match users up by email address, however if a user changes their email address on the front end and then attempts to visit the community, there will be no way to match the accounts up, typically resulting in a new account being created for the (existing) user.

Note that the OAuth support in Invision Community facilitates matching remote user IDs to local user IDs, so if you will be creating a custom login handler in addition to an SSO plugin you may wish to (1) map that custom login handler in the updateAfterAuthentication() call above and (2) use the built in login handler linking tables to map the remote ID, instead of modifying the core_members database table.

Warning

As with running the IPS Community Suite as a "Master", when IPS4 is configured as the "Slave" you may wish to redirect registration, login, logout, change password and change email requests for the community to your remote application. If so, you will need to either create hooks to redirect these requests to your remote application, or you will need to create a custom login handler that can propagate the requests to your remote application.

Every SSO implementation is different, so you will need to determine the best approach for your specific needs.


  Report Document


×
×
  • Create New...