[ * 'class' => 'yii\authclient\AuthAction', * 'successCallback' => [$this, 'successCallback'], * ], * ] * } * * public function successCallback($client) * { * $atributes = $client->getUserAttributes(); * // user login or signup comes here * } * } * ~~~ * * Usually authentication via external services is performed inside the popup window. * This action handles the redirection and closing of popup window correctly. * * @see Collection * @see \yii\authclient\widgets\Choice * * @author Paul Klimov * @since 2.0 */ class AuthAction extends Action { /** * @var string name of the auth client collection application component. * It should point to [[Collection]] instance. */ public $clientCollection = 'authClientCollection'; /** * @var string name of the GET param, which is used to passed auth client id to this action. * Note: watch for the naming, make sure you do not choose name used in some auth protocol. */ public $clientIdGetParamName = 'authclient'; /** * @var callable PHP callback, which should be triggered in case of successful authentication. * This callback should accept [[ClientInterface]] instance as an argument. * For example: * * ~~~ * public function onAuthSuccess($client) * { * $atributes = $client->getUserAttributes(); * // user login or signup comes here * } * ~~~ * * If this callback returns [[Response]] instance, it will be used as action response, * otherwise redirection to [[successUrl]] will be performed. * */ public $successCallback; /** * @var string the redirect url after successful authorization. */ private $_successUrl = ''; /** * @var string the redirect url after unsuccessful authorization (e.g. user canceled). */ private $_cancelUrl = ''; /** * @var string name or alias of the view file, which should be rendered in order to perform redirection. * If not set default one will be used. */ public $redirectView; /** * @param string $url successful URL. */ public function setSuccessUrl($url) { $this->_successUrl = $url; } /** * @return string successful URL. */ public function getSuccessUrl() { if (empty($this->_successUrl)) { $this->_successUrl = $this->defaultSuccessUrl(); } return $this->_successUrl; } /** * @param string $url cancel URL. */ public function setCancelUrl($url) { $this->_cancelUrl = $url; } /** * @return string cancel URL. */ public function getCancelUrl() { if (empty($this->_cancelUrl)) { $this->_cancelUrl = $this->defaultCancelUrl(); } return $this->_cancelUrl; } /** * Creates default {@link successUrl} value. * @return string success URL value. */ protected function defaultSuccessUrl() { return Yii::$app->getUser()->getReturnUrl(); } /** * Creates default {@link cancelUrl} value. * @return string cancel URL value. */ protected function defaultCancelUrl() { return Yii::$app->getRequest()->getAbsoluteUrl(); } /** * Runs the action. */ public function run() { if (!empty($_GET[$this->clientIdGetParamName])) { $clientId = $_GET[$this->clientIdGetParamName]; /** @var \yii\authclient\Collection $collection */ $collection = Yii::$app->getComponent($this->clientCollection); if (!$collection->hasClient($clientId)) { throw new NotFoundHttpException("Unknown auth client '{$clientId}'"); } $client = $collection->getClient($clientId); return $this->auth($client); } else { throw new NotFoundHttpException(); } } /** * @param mixed $client auth client instance. * @return Response response instance. * @throws \yii\base\NotSupportedException on invalid client. */ protected function auth($client) { if ($client instanceof OpenId) { return $this->authOpenId($client); } elseif ($client instanceof OAuth2) { return $this->authOAuth2($client); } elseif ($client instanceof OAuth1) { return $this->authOAuth1($client); } else { throw new NotSupportedException('Provider "' . get_class($client) . '" is not supported.'); } } /** * This method is invoked in case of successful authentication via auth client. * @param ClientInterface $client auth client instance. * @throws InvalidConfigException on invalid success callback. * @return Response response instance. */ protected function authSuccess($client) { if (!is_callable($this->successCallback)) { throw new InvalidConfigException('"' . get_class($this) . '::successCallback" should be a valid callback.'); } $response = call_user_func($this->successCallback, $client); if ($response instanceof Response) { return $response; } return $this->redirectSuccess(); } /** * Redirect to the given URL or simply close the popup window. * @param mixed $url URL to redirect, could be a string or array config to generate a valid URL. * @param boolean $enforceRedirect indicates if redirect should be performed even in case of popup window. * @return \yii\web\Response response instance. */ public function redirect($url, $enforceRedirect = true) { $viewFile = $this->redirectView; if ($viewFile === null) { $viewFile = __DIR__ . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'redirect.php'; } else { $viewFile = Yii::getAlias($viewFile); } $viewData = [ 'url' => $url, 'enforceRedirect' => $enforceRedirect, ]; $response = Yii::$app->getResponse(); $response->content = Yii::$app->getView()->renderFile($viewFile, $viewData); return $response; } /** * Redirect to the URL. If URL is null, {@link successUrl} will be used. * @param string $url URL to redirect. * @return \yii\web\Response response instance. */ public function redirectSuccess($url = null) { if ($url === null) { $url = $this->getSuccessUrl(); } return $this->redirect($url); } /** * Redirect to the {@link cancelUrl} or simply close the popup window. * @param string $url URL to redirect. * @return \yii\web\Response response instance. */ public function redirectCancel($url = null) { if ($url === null) { $url = $this->getCancelUrl(); } return $this->redirect($url, false); } /** * Performs OpenID auth flow. * @param OpenId $client auth client instance. * @return Response action response. * @throws Exception on failure. * @throws HttpException on failure. */ protected function authOpenId($client) { if (!empty($_REQUEST['openid_mode'])) { switch ($_REQUEST['openid_mode']) { case 'id_res': if ($client->validate()) { return $this->authSuccess($client); } else { throw new HttpException(400, 'Unable to complete the authentication because the required data was not received.'); } break; case 'cancel': $this->redirectCancel(); break; default: throw new HttpException(400); break; } } else { $url = $client->buildAuthUrl(); return Yii::$app->getResponse()->redirect($url); } return $this->redirectCancel(); } /** * Performs OAuth1 auth flow. * @param OAuth1 $client auth client instance. * @return Response action response. */ protected function authOAuth1($client) { // user denied error if (isset($_GET['denied'])) { return $this->redirectCancel(); } if (isset($_REQUEST['oauth_token'])) { $oauthToken = $_REQUEST['oauth_token']; } if (!isset($oauthToken)) { // Get request token. $requestToken = $client->fetchRequestToken(); // Get authorization URL. $url = $client->buildAuthUrl($requestToken); // Redirect to authorization URL. return Yii::$app->getResponse()->redirect($url); } else { // Upgrade to access token. $accessToken = $client->fetchAccessToken(); return $this->authSuccess($client); } } /** * Performs OAuth2 auth flow. * @param OAuth2 $client auth client instance. * @return Response action response. * @throws \yii\base\Exception on failure. */ protected function authOAuth2($client) { if (isset($_GET['error'])) { if ($_GET['error'] == 'access_denied') { // user denied error return $this->redirectCancel(); } else { // request error if (isset($_GET['error_description'])) { $errorMessage = $_GET['error_description']; } elseif (isset($_GET['error_message'])) { $errorMessage = $_GET['error_message']; } else { $errorMessage = http_build_query($_GET); } throw new Exception('Auth error: ' . $errorMessage); } } // Get the access_token and save them to the session. if (isset($_GET['code'])) { $code = $_GET['code']; $token = $client->fetchAccessToken($code); if (!empty($token)) { return $this->authSuccess($client); } else { return $this->redirectCancel(); } } else { $url = $client->buildAuthUrl(); return Yii::$app->getResponse()->redirect($url); } } }