Yii2 - How to write OAuth Client - oauth-2.0

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.

Related

Yii 2 - authclient doesn't call onAuthSuccess

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.

SQS RedrivePolicy PHP SDK

Iam new to SQS php SDK, Iam not able to override RedrivePolicy using setQueueAttributes method :(
json string is not accepted as an attribute and I cannot find any clear resources to help me.
Have a look at the below example code:
$queueUrl = "QUEUE_URL";
$client = new SqsClient([
'profile' => 'default',
'region' => 'us-west-2',
'version' => '2012-11-05'
]);
try {
$result = $client->setQueueAttributes(array(
'Attributes' => [
'ReceiveMessageWaitTimeSeconds' => 20
],
'QueueUrl' => $queueUrl, // REQUIRED
));
var_dump($result);
} catch (AwsException $e) {
// output error message if fails
error_log($e->getMessage());
}
Full code:
https://github.com/awsdocs/aws-doc-sdk-examples/blob/master/php/example_code/sqs/LongPollingSetQueueAttributes.php

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);

PHPUnit ZF2 InputFilter with Custom Validator

I have the following InputFilter:
<?php
namespace Login\InputFilter;
use Zend\InputFilter\InputFilter;
/**
* Class Login
*
* #package Login\InputFilter
*/
class Login extends InputFilter
{
/**
* Construct
*/
public function __construct()
{
/**
* Password
*/
$this->add(
[
'name' => 'password',
'required' => true,
'filters' => [
[
'name' => 'stringtrim'
]
],
'validators' => [
[
'name' => 'stringlength',
'options' => [
'min' => '5',
'max' => '128'
],
'break_chain_on_failure' => true
],
[
'name' => 'regex',
'options' => [
'pattern' => '/^[^\\\' ]+$/'
],
'break_chain_on_failure' => true
]
]
]
);
}
/**
* Init
*/
public function init()
{
/**
* Employee ID
*/
$this->add(
[
'name' => 'employeeId',
'required' => true,
'filters' => [
[
'name' => 'stringtrim'
]
],
'validators' => [
[
'name' => 'stringlength',
'options' => [
'min' => '1',
'max' => '20'
],
'break_chain_on_failure' => true
],
[
'name' => 'digits',
'break_chain_on_failure' => true
],
[
'name' => 'Login\Validator\EmployeeId',
'break_chain_on_failure' => true
]
]
]
);
}
}
Attached to the employeeId is a custom validator I've created to check if the Employee ID actually exists in a database. It has a constructor for Doctrine Entity Manager. This works fine when testing via the web, so no worries there.
However now I would like to test via PHPUnit and I've created the following test:
<?php
namespace LoginTest\InputFilter;
use Login\InputFilter\Login;
/**
* Class LoginTest
*
* #package LoginTest\InputFilter
*/
class LoginTest extends \PHPUnit_Framework_TestCase
{
/**
* #var Login $inputFilter
*/
protected $inputFilter;
public function setUp()
{
$this->inputFilter = new Login();
$this->inputFilter->init();
parent::setUp();
}
public function testFormHasElements()
{
$inputs = $this->inputFilter->getInputs();
$this->assertArrayHasKey(
'employeeId',
$inputs
);
$this->assertArrayHasKey(
'password',
$inputs
);
}
}
When the test runs the following error is produced:
1) LoginTest\InputFilter\LoginTest::testFormHasElements
Argument 1 passed to Login\Validator\EmployeeId::__construct() must be an instance of Doctrine\ORM\EntityManager, none given, called in /vhosts/admin-application/vendor/zendframework/zendframework/library/Zend/ServiceManager/AbstractPluginManager.php on line 180 and defined
I'm not certain how I can get passed this particular error. I assume I need to use Mockery but I'm not certain.
The validator has a Factory which supplies the Doctrine Entity Manager from the Service Locator.
I am still very new to PHPUnit but I've been trying to do my research before asking here.
Any ideas?
You're getting this error because you directly instantiate you input filter and it isn't then aware of your custom validator factory.
In real application InputFilter is using Zend\Validator\ValidatorPluginManager for getting validators from service manager.
I see two ways how to solve this problem:
1.) You can setup real service manager from application configuration, like it's described in documentation and then pull the input filter from service manager:
$inputFilter = Bootstrap::getServiceManager()->get(\Login\InputFilter\Login::class); // change the service name if you have another
This solution is good if you want to write some kind of integration tests.
2.) You can mock your custom validator and inject into ValidatorPluginManager in setup method:
protected function setUp()
{
$validator = $this->getMockBuilder(\Login\Validator\EmployeeId::class)->getMock();
$inputFilter = new Login();
$inputFilter->getFactory()
->getDefaultValidatorChain()
->getPluginManager()
->setService(\Login\Validator\EmployeeId::class, $validator);
$inputFilter->init();
$this->inputFilter = $inputFilter;
parent::setUp();
}
This solution is good if you want to write unit tests for Login input filter.

Resources