Invision Community 4: SEO, prepare for v5 and dormant account notifications By Matt Monday at 02:04 PM
teez Posted July 15, 2018 Posted July 15, 2018 Hi. I'm trying to implement Single Sign On like it is described here: https://invisioncommunity.com/developers/docs/members-authentication/developing-single-sign-on-sso-integration-r65/ I have created a plugin which extends \IPS\Session\Front and I know i should implement read($session) method. I have an endpoint on my oauth2 external server. He tells me when user is logged in there, but i don't know what is the best way to login user. I was looking for any method that do it for me. I don't want to to reinvent the wheel. ? I have created a Custom OAuth2 login method handler. Currently I do just a trick in HTML - via jquery I'm checking if user is logged on external page and then "submit" form login in background. It is working but i know it is a little cheat.
bfarber Posted July 16, 2018 Posted July 16, 2018 The general approach we take in this type of plugin is Call the parent method Determine if the user is logged in locally already and if so, just return. Sometimes we also check if the user appears to be logged out of the third party source (e.g. no session cookie from the front end set) and log the user out locally in this case, but most of the time this isn't super necessary. If the user is not logged in, look for whatever identifier the front end sets to see if the user is logged in. If that's present, call the external source to validate the identifier and get whatever details are available (e.g. name and email address). If that succeeds we know the user is logged in to the external source. At this point we attempt to load the user by a guid or other unique id, and if this succeeds we set the member in the session class, call the device handler method to update the login, and then return the member. If there is no local user yet, we create them first and then set the user as logged in and call the device handler method. Here is a copy of an OAuth2 SSO integration we have, more or less //<?php /** * oAuth2 SSO - Session/Front Hook * * @copyright (c) 2001 - 2017 Invision Power Services, Inc. * @link https://invisioncommunity.com */ class hook1 extends _HOOK_CLASS_ { /** * 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() ); /* Are we being sent here from the front end with a code? This could be changed to pull an identifier from a cookie alternatively. */ if( !\IPS\Request::i()->code OR ( \IPS\Request::i()->module == 'system' AND \IPS\Request::i()->controller == 'ajax' ) OR $this->member->member_id ) { return $result; } /* Settings Map */ $userId = \IPS\Settings::i()->oAuthSso_uidField; $userName = \IPS\Settings::i()->oAuthSso_nameField; $emailAddress = \IPS\Settings::i()->oAuthSso_emailField; /** * Exchange code for access token */ try { $request = \IPS\Http\Url::external( \IPS\Settings::i()->oAuthSso_tokenEndpoint ) ->request() ->post([ 'client_id' => \IPS\Settings::i()->oAuthSso_clientId, 'client_secret' => \IPS\Settings::i()->oAuthSso_clientSecret, 'code' => \IPS\Request::i()->code, 'redirect_uri' => rtrim( \IPS\Settings::i()->base_url, '/' ), 'format' => 'json', 'grant_type' => 'authorization_code' ]) ->decodeJson( FALSE ); /* Check for access token */ if( !isset( $request->access_token ) OR !$request->access_token ) { return $result; } /* Get User info */ $userInfo = \IPS\Http\Url::external( \IPS\Settings::i()->oAuthSso_userEndpoint ) ->setQueryString( [ 'format' => 'json' ] ) ->request() ->setHeaders( array( 'Authorization' => "Bearer {$request->access_token}" ) ) ->get() ->decodeJson( FALSE ); if( !isset( $userInfo->$userId ) ) { throw new \InvalidArgumentException( 'MISSING_PARAMETER' ); } } /* Any exception here is a bad thing */ catch( \Exception $e ) { return $result; } /* Disable Guest Cache */ $noCacheExpire = new \IPS\DateTime; $noCacheExpire->add( new \DateInterval( 'PT3600S' ) ); \IPS\Request::i()->setCookie( 'noCache', 1, $noCacheExpire ); /* Load user */ $this->member = \IPS\Member::load( $userInfo->$userId, 'oauth_id' ); if( !$this->member->member_id AND $userInfo->$emailAddress ) { $this->member = \IPS\Member::load( $userInfo->$emailAddress, 'email' ); } /* Is this a new account? - Don't reset member group on existing accounts */ if( !$this->member->member_id ) { $this->member->member_group_id = \IPS\Settings::i()->member_group; } /* Populate oAuth ID */ if( !$this->member->oauth_id ) { $this->member->oauth_id = $userInfo->$userId; } /* Check Email Address */ if( $userInfo->$emailAddress AND $this->member->email != $userInfo->$emailAddress AND !empty( $userInfo->$emailAddress ) ) { $this->member->email = $userInfo->$emailAddress; } /* Check Name */ if( $userName AND $this->member->name != $userInfo->$userName AND !empty( $userInfo->$userName ) ) { $this->member->name = $userInfo->$userName; } $this->member->save(); /* At this point, we're logged in. - We cannot call setMember here because it will not work with PHP 7.1 * -- We cannot call session_regenerate_id() from within the session read. */ $_SESSION['forcedWrite'] = time(); /* Make sure session handler saves during write() */ $this->save = TRUE; /* R/W Sep means we need to just go ahead and set this */ \IPS\Member::$loggedInMember = $this->member; /* Set up device and enable 'remember me' */ \IPS\Member\Device::loadOrCreate( $this->member )->updateAfterAuthentication( TRUE, \IPS\Login\Handler::findMethod( 'IPS\Login\Handler\Standard' ) ); /* And we're logged in */ return $result; } }
Recommended Posts
Archived
This topic is now archived and is closed to further replies.