Yii 2 - authclient doesn't call onAuthSuccess - oauth

I am using Yii authclient to use social login. I had set everything as it is defined in docs but when I try to login with google it does not call onAuthSuccess method. When I try to login it just redirects me to returnUrl but not authenticated.
Here is my code;
config/main.php
'authClientCollection' => [
'class' => \yii\authclient\Collection::class,
'clients' => [
'google' => [
'class' => \yii\authclient\clients\Google::class,
'clientId' => *********, //changed for issue purpose
'clientSecret' => *********, //changed for issue purpose
'returnUrl' => 'http://localhost/site/landing',
],
],
]
controllers/SiteController
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['logout', 'signup', 'auth'],
'rules' => [
[
'actions' => ['signup', 'auth'],
'allow' => true,
'roles' => ['?'],
],
[
'actions' => ['logout'],
'allow' => true,
'roles' => ['#'],
],
],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'logout' => ['post'],
'create-storyboard' => ['post'],
],
],
];
}
/**
* {#inheritdoc}
*/
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
],
'auth' => [
'class' => 'yii\authclient\AuthAction',
'successCallback' => [$this, 'onAuthSuccess'],
],
];
}
public function onAuthSuccess($client)
{
(new AuthHandler($client))->handle();
}

If you set returnUrl the user is from auth provider redirected directly to the url you've set in that property.
In your case the returnUrl says google, that it should redirect user to http://localhost/site/landing. But there is nothing in your site/landing action that would call the onAuthSuccess.
You need to let user come back to site/auth and redirect them after processing response from OAuth provider. To do that remove the returnUrl from config. That will make the authclient to use default return url which is the action that started the auth process.
Then modify your onAuthSuccess to redirect users to site/landing like this:
public function onAuthSuccess($client)
{
(new AuthHandler($client))->handle();
$this->redirect(['site/landing']);
}

I had solved the problem with the help from #Michal HynĨica. The problem was in my returnUrl which means with authentication url it must follow to authenticate rather the redirecting after authentication. So all I need to do was changing it to as below.
'returnUrl' => 'http://localhost/site/auth?authclient=google'
Also don't forget to add same returnUrl to your google console's redirect url.

Related

Yii2 - How to write OAuth Client

I am trying to add FitBit OAuth to my Yii2 app. I have successfully managed to setup other APIs, such as Google and Facebook - but these are using yii\authclient\clients. Here I am trying to write my own using yii\authclient\OAuth2. Here is the code I have:
<?php
namespace app\auth\clients;
class Fitbit extends \yii\authclient\OAuth2{
public $authUrl = 'https://www.fitbit.com/oauth2/authorize';
public $tokenUrl = 'https://api.fitbit.com/oauth2/token';
public $apiBaseUrl = 'https://api.fitbit.com';
public $scope = 'profile';
protected function initUserAttributes(){
return $this->api('/1/user', 'GET');
}
protected function defaultName(){
return 'fitbit';
}
protected function defaultTitle(){
return 'Fitbit';
}
}
Here is the configuration I have:
'authClientCollection' => [
'class' => 'yii\authclient\Collection',
'clients' => [
'google' => [
'class' => 'yii\authclient\clients\Google',
'clientId' => 'ID',
'clientSecret' => 'SECRET',
],
'fitbit' => [
'class' => 'app\auth\clients\Fitbit',
'clientId' => 'ID',
'clientSecret' => 'SECRET',
],
But I am stumped, as this is the error I get:
{"errors":[{
"errorType":"invalid_request",
"message":"Authorization header required. Visit https://dev.fitbit.com/docs/oauth2 for more information on the Fitbit Web API authorization process."
}],
"success":false}
Any help would be really appreciated. Thank you.
EDIT My current thinking is something along the lines of:
return $this->api('/1/user', 'GET', [], [
'WWW-Authenticate' => 'Authorization: Basic ' . base64_encode($this->client_id . ':' . $this->client_secret),
]);
But this still gives me the same error.

Zend Framework 2 - Controller instantiate Service via (modified?) Factory

I have an abstract service class SAbstract which is inherited by ConcreteServiceA and ConcreteServiceB. Now I am instantiating ConcreteServiceA in the factory class of my controller and inject the service in my controller.
In a specific action in my controller I want to exchange ConcreteServiceA with ConcreteServiceB to change behavior. Because they have same interface (abstract class SAbstract) I could inject it in my controller as well (the services are a Strategy-Pattern).
But I don't want to instantiate ConcreteServiceB directly in my controller to keep my code clean (for easy refactoring and exchanging behavior).
A possible solution is to create a second factory for my controller which injects ConcreteServiceB instead of ConcreteServiceA but then I have duplicated lots of code which is not good...
Another solution would be to inject both services in my controller (but this "smells" like bad code).
Is a delegator factory the right way to do this? Then I have to implement setters in my controller...
Is there a better way?
I tried to schematically visualize my class relationships.
AbstractService <|--<inherit>- ConcreteServiceA
AbstractService <|--<inherit>- ConcreteServiceB
Controller -<use>--> AbstractService
Controller:ActionA -<use>--> ConcreteServiceA:exportAction()
Controller:ActionB -<use>--> ConcreteServiceB:exportAction()
In a specific action in my controller I want to exchange ConcreteServiceA with ConcreteServiceB to change behavior. Because they have same interface.
You can configure the route to use a different controller service name for each action; then configure a controller factory to inject the required service using configuration.
The route config could look like this.
'router' => [
'routes' => [
'foo' => [
'type' => 'literal',
'options' => [
'route' => '/foo',
'defaults' => [
'controller' => 'MyControllerWithFooService',
'action' => 'actionThatNeedsFooService',
],
],
],
'bar' => [
'type' => 'literal',
'options' => [
'route' => '/bar',
'defaults' => [
'controller' => 'MyControllerWithBarService',
'action' => 'actionThatNeedsBarService',
],
],
],
],
]
Then add the config for the services and controllers.
'app_config' => [
'MyControllerWithFooService' => [
'service_name' => 'FooService',
],
'MyControllerWithFooService' => [
'service_name' => 'BarService',
],
],
'service_manager' => [
'factories' => [
'FooService' => 'FooServiceFactory'
'BarService' => 'BarServiceFactory'
],
],
'controllers' => [
'factories' => [
'MyControllerWithFooService' => 'MyControllerServiceFactory'
'MyControllerWithBarService' => 'MyControllerServiceFactory'
],
]
The MyControllerServiceFactory could be very simple.
class MyControllerServiceFactory
{
public function __invoke($controllerManager, $name, $requestedName)
{
$sm = $controllerManager->getServiceLocator();
$config = $sm->get('config');
if (empty($config['app_config'][$requestedName])) {
throw new ServiceNotCreatedException('No config set!');
}
$serviceName = $config['app_config'][$requestedName]['service_name'];
$service = $sm->get($serviceName);
return new MyController($service);
}
}

Zfcuser route has a child route

I'm working on a ZF2 project, and using ZfcUser to manage website users.
I just want to know is it possible to have a child route to the zfcuser route?
Something like that, in the configuration of my module:
return [
'router' =>
[
'routes' =>
[
'admin' =>
[
'type' => 'Segment',
'options' =>
[
'route' => '/admin',
'defaults' =>
[
'__NAMESPACE__' => 'admin',
'controller' => 'admin.index',
'action' => 'index',
],
],
'may_terminate' => true,
'child_routes' =>
[
'zfcuser' =>
[
'type' => 'Literal',
'options' =>
[
'route' => '/account',
]
],
],
],
],
],
];
This is a problem I've tried overcoming recently, not using ZfcUser but whilst developing my own modules. There are a few solutions but the most suitable would depend on the type of application you're developing.
The easiest solution would be to override the zfcuser route path and prefixing it with admin.
return [
'router' => [
'routes' => [
'zfcuser' => [
'options' => [
'route' => '/admin/user',
],
],
],
],
];
If you're like me and want all you admin routes contained under a single route then you're better off removing the zfcuser route completely and implementing your own which could utilize the ZfcUser controllers.
namespace Application;
use Zend\ModuleManager\ModuleEvent;
use Zend\ModuleManager\ModuleManager;
class Module
{
public function init(ModuleManager $moduleManager)
{
$events = $moduleManager->getEventManager();
$events->attach(ModuleEvent::EVENT_MERGE_CONFIG, array($this, 'onMergeConfig'));
}
public function onMergeConfig(ModuleEvent $event)
{
$configListener = $event->getConfigListener();
$configuration = $configListener->getMergedConfig(false);
if (isset($configuration['router']['routes']['zfcuser']))
{
unset($configuration['router']['routes']['zfcuser']);
}
$configListener->setMergedConfig($configuration);
}
}

AuthAction in yii2

I'm trying to implement yii\authclient\AuthAction's successCallback.
My code looks like this:
public function actions()
{
return [
'auth' => [
'class' => 'yii\authclient\AuthAction',
'successCallback' => [$this, 'successCallback'],
],
];
}
/**
* #param \yii\authclient\ClientInterface $client
*/
public function successCallback($client)
{
$attributes = $client->getUserAttributes();
$externalUser = new AuthForm();
$externalUser->authProvider = $client->getName();
$externalUser->externalUserId = array_key_exists('id', $attributes) ? $attributes['id'] : null;
if ($externalUser->validate())
{
if ($externalUser->isRegistered())
{
$externalUser->login();
return $this->redirect(['private/index']);
}
else
{
Yii::$app->session->set( 'signup/authProvider', $externalUser->authProvider );
Yii::$app->session->set( 'signup/attributes' , $attributes );
return $this->redirect(['site/signup']);
}
}
}
How can I call successCallback? I want to call the auth method. But I am not able to do this?
Most likely this is working fine, but you did not permit for the action of auth to be accessed. Make sure you allow auth in your behaviours of your controller. Something like:
public function behaviors() {
$behaviors = parent::behaviors();
$behaviors [ 'access' ] = [
'rules' => [
[
'actions' => [ 'auth' ],
'allow' => true,
],
],
];
return $behaviors;
}
It will run successCallback when Auth server response successful.
You must config authcollection (collection config of auth server)
'components' => [
'authClientCollection' => [
'class' => 'yii\authclient\Collection',
'clients' => [
'google' => [
'class' => 'yii\authclient\clients\GoogleOpenId'
],
'facebook' => [
'class' => 'yii\authclient\clients\Facebook',
'clientId' => 'facebook_client_id',
'clientSecret' => 'facebook_client_secret',
],
// etc.
],
]
...
]
Default: Yii2 authclient support some openid, oauth, oauth2 provider:
[[\yii\authclient\clients\Facebook|Facebook]].
[[yii\authclient\clients\GitHub|GitHub]].
Google (via [[yii\authclient\clients\GoogleOpenId|OpenID]] and [[yii\authclient\clients\GoogleOAuth|OAuth]]).
[[yii\authclient\clients\LinkedIn|LinkedIn]].
[[yii\authclient\clients\Live|Microsoft Live]].
[[yii\authclient\clients\Twitter|Twitter]].
[[yii\authclient\clients\VKontakte|VKontakte]].
Yandex (via [[yii\authclient\clients\YandexOpenId|OpenID]] and [[yii\authclient\clients\YandexOAuth|OAuth]]).
There's ready to use [[yii\authclient\widgets\AuthChoice]] widget to use in views:
<?= yii\authclient\widgets\AuthChoice::widget([
'baseAuthUrl' => ['site/auth'],
'popupMode' => false,
]) ?>
For more infomation: https://github.com/yiisoft/yii2-authclient/tree/master/docs/guide
Goodluck and have fun!

From Backend to Frontend Yii2 Advanced App

I'm trying to link some controllers from frontend to backend. After some hours I don't where could be the problem.
Backend
file: main.php
'urlManager' => [
'enablePrettyUrl' => false,
'showScriptName' => false,
'baseUrl' => '/backend/web',
],
'urlManagerFrontEnd' => [
'class' => 'yii\web\urlManager',
'baseUrl' => '/frontend/web',
'enablePrettyUrl' => false,
'showScriptName' => false,
]
file: SiteController.php
public function actionIndex()
{
// User's variable
$user = \common\models\User::findIdentity(Yii::$app->user->id);
if($user->role != self::USER_ADMIN){
return $this->redirect(Url::to(Yii::$app->urlManagerFrontEnd->createUrl(['/site/index'])));
}
return $this->render('index');
}
Using this
Url::to(Yii::$app->urlManagerFrontEnd->createUrl(['/site/index']))
Returns me
/advanced/backend/web/index.php?r=site%2Findex
Any advice?
Your code is correct. urlManagerFrontEnd should return url based on baseUrl /frontend/web.
Try change baseUrl to http://yourdomain/
I did little googling and found this link.
for reference I am posting same here
I read around in the UrlManager.php and found the following:
$baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl();
So this means when showScriptName= true and enablePrettyUrl=false $baseUrl = getScriptUrl() otherwise $baseUrl = getBaseUrl()
So it just work with prettyUrl=true and the showScriptName = false. When we set prettyUrl on true it takes $baseUrl = getBaseUrl()
Changing it to the following it resolves our problem =).
/*$baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl();*/
$baseUrl = !$this->showScriptName || $this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl();
Now you have to set prettyurl=false and the other on true et voila
I tried this on a fresh template and then applied code you mentioned in question and got the same error as you got.
But then after the fix I did according to this post I get correct path.
This link is also helpful.
in your frontend config add this to the top to define 2 variables.
use \yii\web\Request;
$baseUrl = str_replace('/frontend/web', '/frontend/web', (new Request)->getBaseUrl());
$backEndBaseUrl = str_replace('/frontend/web', '/backend/web', (new Request)->getBaseUrl());
And set these variables as the baseUrl parameters in the components
'components' => [
'urlManager' => [
'class' => 'yii\web\urlManager',
'enablePrettyUrl' => false,
'showScriptName' => false,
//'baseUrl' => '/frontend/web',
'baseUrl'=> $baseUrl,
],
'urlManagerBackEnd' => [
'class' => 'yii\web\urlManager',
'enablePrettyUrl' => false,
'showScriptName' => false,
//'baseUrl' => '/backend/web',
'baseUrl' => $backEndBaseUrl,
],
then you can have links from the frontend to the backend by e.g.
$backendUrl= Yii::$app->urlManagerBackEnd->createUrl('//');
echo yii\helpers\Html::a('link to backend', $backendUrl);
to have the same from the backend to the frontend add this to the backend config:
use \yii\web\Request;
$baseUrl = str_replace('/backend/web', '/backend/web', (new Request)->getBaseUrl());
$frontEndBaseUrl = str_replace('/backend/web', '/frontend/web', (new Request)->getBaseUrl());
and in the components:
'urlManager' => [
'class' => 'yii\web\urlManager',
'enablePrettyUrl' => false,
'showScriptName' => false,
'baseUrl'=> $baseUrl,
],
'urlManagerFrontEnd' => [
'class' => 'yii\web\urlManager',
'enablePrettyUrl' => false,
'showScriptName' => false,
//'baseUrl' => '/backend/web',
'baseUrl' => $frontEndBaseUrl,
],
and to create links use:
$frontendUrl= Yii::$app->urlManagerFrontEnd->createUrl('//');
echo yii\helpers\Html::a('link to frontend', $frontendUrl);
forgot you can of course also link to specific pages e.g. from backend to frontend site/about:
$frontendUrl= Yii::$app->urlManagerFrontEnd->createUrl('/site/about');
echo yii\helpers\Html::a('link to frontend site about', $frontendUrl);
BTW. if you have removed the /web behavior by some htaccess you should also remove it in the variables.
use this code. it will redirect you to front end
return $this->redirect(Yii::$app->urlManager->createUrl('./../../frontend/web/'));
Use below one:
Url::to(Yii::$app->urlManagerBackEnd->createUrl('index.php/'/site/index'), true);

Resources