Yii2: Authentication with Google and Facebook
This is continuation from https://takdekeje.kuceng.my/2018/08/yii2-validate-email-after-registration.html.
Objective:
- Register and login with Google and Facebook account.
- Link existing account with Google and Facebook.
- Unlink existing account from Google and Facebook.
Part 1: Initial setup
-
Install AuthClient Extension.
composer require "yiisoft/yii2-authclient:*" -
Create new migration to update DB
yii migrate/create authclient -
Edit the migration file:
<?php use yii\db\Migration; /** * Class m180926_083206_auth */ class m180926_083206_auth extends Migration { /** * {@inheritdoc} */ public function safeUp() { } /** * {@inheritdoc} */ public function safeDown() { echo "m180926_083206_auth cannot be reverted.\n"; return false; }// Use up()/down() to run migration code without a transaction. public function up() { $this->createTable('auth', [ 'id' => 'bigint UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT', 'uid' => 'bigint UNSIGNED NOT NULL', 'source' => $this->string()->notNull(), 'source_id' => $this->string()->notNull(), ]); } public function down() { $this->dropTable('auth'); }} -
Run the migration.
Migration error? Don't blindly copy paste the whole code. Understand it, then you can modify the blue part.yii migrate up - Generate model for table auth using gii.
-
Create component AuthHandler
<?php namespace app\components; use app\models\Auth; use app\models\Users; use Yii; use yii\authclient\ClientInterface; use yii\helpers\ArrayHelper; /** * AuthHandler handles successful authentication via Yii auth component */ class AuthHandler { /** * @var ClientInterface */ private $client; private $client_id; public function __construct(ClientInterface $client) { $this->client = $client; $this->client_id = $client->id; } public function handle() { $attributes = $this->client->getUserAttributes(); switch($this->client_id) { case 'google': $email = ArrayHelper::getValue($attributes, 'emails.0.value'); $id = ArrayHelper::getValue($attributes, 'id'); break; default: $email = ArrayHelper::getValue($attributes, 'email'); $id = ArrayHelper::getValue($attributes, 'id'); } /* @var Auth $auth */ $auth = Auth::find()->where([ 'source' => $this->client->getId(), 'source_id' => $id, ])->one(); if (Yii::$app->user->isGuest) { if ($auth) { // login /* @var User $user */ $user = Users::find()->where(['id' => $auth->uid])->one(); Yii::$app->user->login($user); } else { // signup if ($email !== null && Users::find()->where(['email' => $email])->exists()) { Yii::$app->getSession()->setFlash('error', [ Yii::t('app', "User with the same email as in {client} account already exists but isn't linked to it. Login using email first to link it.", ['client' => $this->client->getTitle()]), ]); } else { $password = Yii::$app->security->generateRandomString(8); $user = new Users([ 'email' => $email, 'password' => $password, 'password_repeat' => $password, 'confirmed_at' => date('Y-m-d H:i:s'), ]); $transaction = Users::getDb()->beginTransaction(); if ($user->save()) { $auth = new Auth([ 'uid' => $user->id, 'source' => $this->client->getId(), 'source_id' => (string)$id, ]); if ($auth->save()) { $transaction->commit(); Yii::$app->user->login($user); $user->last_login_at = date('Y-m-d H:i:s'); $user->last_login_ip = \Yii::$app->request->userIP; $user->save(0); Yii::$app->getSession()->setFlash('success', 'Thank you, registration is now complete. If you want to login directly, this is your password: '.$password); } else { Yii::$app->getSession()->setFlash('error', 'Unable to save '.$this->client->getTitle().' account: '.json_encode($auth->getErrors())); } } else { Yii::$app->getSession()->setFlash('error', 'Unable to save user: '.json_encode($user->getErrors())); } } } } else { // user already logged in if (!$auth) { // add auth provider $auth = new Auth([ 'uid' => Yii::$app->user->id, 'source' => $this->client->getId(), 'source_id' => (string)$attributes['id'], ]); if ($auth->save()) { Yii::$app->getSession()->setFlash('success', 'Successfully linked '.$this->client->getTitle().' account.'); } else { Yii::$app->getSession()->setFlash('error', 'Unable to link '.$this->client->getTitle().' account: '.json_encode($auth->getErrors())); } } else { // there's existing auth Yii::$app->getSession()->setFlash('error', 'Unable to link '.$this->client->getTitle().' account. There is another user using it.'); } } } } -
Modify SiteController
<?php namespace app\controllers; use Yii; use yii\filters\AccessControl; use yii\web\Controller; use yii\web\Response; use yii\filters\VerbFilter; use app\models\LoginForm; use app\models\ContactForm;use app\components\AuthHandler;class SiteController extends Controller { /** * {@inheritdoc} */ public function behaviors() { return [ 'access' => [ 'class' => AccessControl::className(), 'only' => ['logout'], 'rules' => [[ // auth action need to be public accessible 'actions' => ['auth'], 'allow' => true, 'roles' => ['*'], ],['actions' => ['logout'], 'allow' => true, 'roles' => ['@'], ], ], ], 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ 'logout' => ['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(); return $this->redirect(["site/index"]); }/** * Displays homepage. * * @return string */ public function actionIndex() { return $this->render('index'); } /** * Login action. * * @return Response|string */ public function actionLogin() { if (!Yii::$app->user->isGuest) { return $this->goHome(); } $model = new LoginForm(); if ($model->load(Yii::$app->request->post()) && $model->login()) { return $this->goBack(); } $model->password = ''; return $this->render('login', [ 'model' => $model, ]); } /** * Logout action. * * @return Response */ public function actionLogout() { Yii::$app->user->logout(); return $this->goHome(); } /** * Displays contact page. * * @return Response|string */ public function actionContact() { $model = new ContactForm(); if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) { Yii::$app->session->setFlash('contactFormSubmitted'); return $this->refresh(); } return $this->render('contact', [ 'model' => $model, ]); } /** * Displays about page. * * @return string */ public function actionAbout() { return $this->render('about'); } }
- Create project at https://console.developers.google.com/project Note the Project ID
-
Setup credentials at
https://console.developers.google.com/project/[yourProjectId]/apiui/credential
If you click the link, replace [yourProjectId] with your Project ID in the URL bar!
- You need Client ID and Client secret of your project. Click on the project's name
-
Enable Google+ API at
https://console.developers.google.com/project/[yourProjectId]/apiui/api/plus
Again, if you just click on the link, change [yourProjectId] to your actual Project ID in the URL bar.
If you got "blank screen", try to click on the main menu APIs & Services, until you can find something to enable APIs and Services. You might see a lot of API library. Look for Google+ API and enable it. - In config/web.php, add authClientCollection under components
'components' => ['authClientCollection' => [// ... ]
'class' => 'yii\authclient\Collection',
'clients' => [
'google' => [
'class' => 'yii\authclient\clients\Google',
'clientId' => 'google_client_id',
'clientSecret' => 'google_client_secret',
],
],
] -
Modify view users/create and site/login to have button to login with Google.
<?php use yii\helpers\Html; use yii\widgets\ActiveForm; /* @var $this yii\web\View */ /* @var $model app\models\Users */ /* @var $form ActiveForm */ $this->title = 'Sign up'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="row"> <div class="col-md-4 col-md-offset-4 col-sm-6 col-sm-offset-3"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"><?= Html::encode($this->title) ?></h3> </div> <div class="panel-body"> <?php $form = ActiveForm::begin(); ?> <?= $form->field($model, 'email') ?> <?= $form->field($model, 'password')->passwordInput() ?> <?= $form->field($model, 'password_repeat')->passwordInput() ?> <?= Html::submitButton('Sign up', ['class' => 'btn btn-success btn-block']) ?> <?php ActiveForm::end(); ?> </div><?= yii\authclient\widgets\AuthChoice::widget([</div> <p class="text-center"> <?= Html::a('Already registered? Sign in!', ['/site/login']) ?> </p> </div> </div>
'baseAuthUrl' => ['site/auth'],
'popupMode' => false,
]) ?><?php /* @var $this yii\web\View */ /* @var $form yii\bootstrap\ActiveForm */ /* @var $model app\models\LoginForm */ use yii\helpers\Html; use yii\bootstrap\ActiveForm; $this->title = 'Login'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="site-login"> <h1><?= Html::encode($this->title) ?></h1> <p>Please fill out the following fields to login:</p> <?php $form = ActiveForm::begin([ 'id' => 'login-form', 'layout' => 'horizontal', 'fieldConfig' => [ 'template' => "{label}\n<div class=\"col-lg-3\">{input}</div>\n<div class=\"col-lg-8\">{error}</div>", 'labelOptions' => ['class' => 'col-lg-1 control-label'], ], ]); ?> <?= $form->field($model, 'email', ['errorOptions' => ['class' => 'help-block' ,'encode' => false]])->textInput(['autofocus' => true]) ?> <?= $form->field($model, 'password')->passwordInput() ?> <?= $form->field($model, 'rememberMe')->checkbox([ 'template' => "<div class=\"col-lg-offset-1 col-lg-3\">{input} {label}</div>\n<div class=\"col-lg-8\">{error}</div>", ]) ?> <div class="form-group"> <div class="col-lg-offset-1 col-lg-11"> <?= Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) ?> <?= Html::a('Forget Password', ['/users/request'], ['class'=>'btn btn-primary']) ?> </div> </div> <?php ActiveForm::end(); ?><?= yii\authclient\widgets\AuthChoice::widget([</div>
'baseAuthUrl' => ['site/auth'],
'popupMode' => false,
]) ?>
Part 3: Link/Unlink Account
-
Modify SiteController (This is optional - To allow redirect back to Link/Unlink page. You also need to add the URL users/link in Authorize redirect URIs in your Google APIs)
<?php namespace app\controllers; use Yii; use yii\filters\AccessControl; use yii\web\Controller; use yii\web\Response; use yii\filters\VerbFilter; use app\models\LoginForm; use app\models\ContactForm; use app\components\AuthHandler; class SiteController extends Controller { /** * {@inheritdoc} */ public function behaviors() { return [ 'access' => [ 'class' => AccessControl::className(), 'only' => ['logout'], 'rules' => [ [ // auth action need to be public accessible 'actions' => ['auth'], 'allow' => true, 'roles' => ['*'], ],[ 'actions' => ['logout'], 'allow' => true, 'roles' => ['@'], ], ], ], 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ 'logout' => ['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) {if (isset($_GET) && isset($_GET['return'])) { $return = $_GET['return']; } else { $return = "site/index"; }(new AuthHandler($client))->handle(); return $this->redirect([$return]); } /** * Displays homepage. * * @return string */ public function actionIndex() { return $this->render('index'); } /** * Login action. * * @return Response|string */ public function actionLogin() { if (!Yii::$app->user->isGuest) { return $this->goHome(); } $model = new LoginForm(); if ($model->load(Yii::$app->request->post()) && $model->login()) { return $this->goBack(); } $model->password = ''; return $this->render('login', [ 'model' => $model, ]); } /** * Logout action. * * @return Response */ public function actionLogout() { Yii::$app->user->logout(); return $this->goHome(); } /** * Displays contact page. * * @return Response|string */ public function actionContact() { $model = new ContactForm(); if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) { Yii::$app->session->setFlash('contactFormSubmitted'); return $this->refresh(); } return $this->render('contact', [ 'model' => $model, ]); } /** * Displays about page. * * @return string */ public function actionAbout() { return $this->render('about'); } } -
Modify UsersController
<?php namespace app\controllers; use Yii; use app\models\Users; use app\models\UsersSearch; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; use app\models\ResendForm; use app\models\RecoveryForm; use yii\helpers\Html; use app\models\Token;use app\models\Auth;/** * UsersController implements the CRUD actions for Users model. */ class UsersController extends Controller { /** * {@inheritdoc} */ public function behaviors() { return [ 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ 'delete' => ['POST'], ], ], ]; } /** * Lists all Users models. * @return mixed */ public function actionIndex() { $searchModel = new UsersSearch(); $dataProvider = $searchModel->search(Yii::$app->request->queryParams); return $this->render('index', [ 'searchModel' => $searchModel, 'dataProvider' => $dataProvider, ]); } /** * Displays a single Users model. * @param string $id * @return mixed * @throws NotFoundHttpException if the model cannot be found */ public function actionView($id) { return $this->render('view', [ 'model' => $this->findModel($id), ]); } /** * Creates a new Users model. * If creation is successful, the browser will be redirected to the 'view' page. * @return mixed */ public function actionCreate() { $model = new Users(); if (!empty(Yii::$app->request->post())) { if ($model->register(Yii::$app->request->post())) { return $this->render('welcome', ['email' => $model->email]); Yii::$app->end(); } else { return $this->render('create', [ 'model' => $model, ]); Yii::$app->end(); } } return $this->render('create', [ 'model' => $model, ]); } /** * Updates an existing Users model. * If update is successful, the browser will be redirected to the 'view' page. * @param string $id * @return mixed * @throws NotFoundHttpException if the model cannot be found */ public function actionUpdate($id) { $model = $this->findModel($id); if ($model->load(Yii::$app->request->post()) && $model->save()) { return $this->redirect(['view', 'id' => $model->id]); } return $this->render('update', [ 'model' => $model, ]); } /** * Deletes an existing Users model. * If deletion is successful, the browser will be redirected to the 'index' page. * @param string $id * @return mixed * @throws NotFoundHttpException if the model cannot be found */ public function actionDelete($id) { $this->findModel($id)->delete(); return $this->redirect(['index']); } /** * Finds the Users model based on its primary key value. * If the model is not found, a 404 HTTP exception will be thrown. * @param string $id * @return Users the loaded model * @throws NotFoundHttpException if the model cannot be found */ protected function findModel($id) { if (($model = Users::findOne($id)) !== null) { return $model; } throw new NotFoundHttpException('The requested page does not exist.'); } /** * Confirms user's account. If confirmation was successful logs the user and shows success message. Otherwise * shows error message. * * @param int $id * @param string $code * * @return string * @throws \yii\web\HttpException */ public function actionConfirm($id, $code) { $user = Users::findOne($id); $user->attemptConfirmation($id, $code); return $this->redirect(['site/index']); } /** * Displays page where user can request new confirmation token. If resending was successful, displays message. * * @return string * @throws \yii\web\HttpException */ public function actionResend() { /** @var ResendForm $model */ $model = new ResendForm; if (!empty(Yii::$app->request->post())) { if ($model->load(\Yii::$app->request->post()) && $model->resend()) { return $this->redirect(['site/index']); Yii::$app->end(); } } return $this->render('resend', [ 'model' => $model, ]); } /** * Shows page where user can request password recovery. * * @return string */ public function actionRequest() { /** @var RecoveryForm $model */ $model = new RecoveryForm; $model->scenario = RecoveryForm::SCENARIO_REQUEST; if (!empty(Yii::$app->request->post())) { if ($model->load(\Yii::$app->request->post()) && $model->sendRecoveryMessage()) { return $this->redirect(['site/index']); Yii::$app->end(); } } return $this->render('request', [ 'model' => $model, ]); } /** * Displays page where user can reset password. * * @param int $id * @param string $code * * @return string * @throws \yii\web\NotFoundHttpException */ public function actionReset($id, $code) { /** @var Token $token */ $token = Token::find()->where(['uid' => $id, 'code' => $code, 'type' => Token::TYPE_RECOVERY])->one(); if (!$token || $token->isExpired()) { \Yii::$app->session->setFlash( 'danger', 'Recovery link is invalid or expired. Please try '.Html::a('requesting', ['/users/request']).' a new one.' ); return $this->redirect(['site/login']); Yii::$app->end(); } /** @var RecoveryForm $model */ $model = new RecoveryForm; $model->scenario = RecoveryForm::SCENARIO_RESET; if (!empty(Yii::$app->request->post())) { if ($model->load(\Yii::$app->getRequest()->post()) && $model->resetPassword($token)) { \Yii::$app->session->setFlash( 'success', 'Password has been changed' ); return $this->redirect(['site/login']); Yii::$app->end(); } } return $this->render('reset', [ 'model' => $model, ]); }
use app\components\AuthHandler;public function actionLink() {}
return $this->render('link');
}
public function actionDisconnect() {
$client = $_POST['client'];
$auth = Auth::find()->where(['uid' => Yii::$app->user->id, 'source' => $client])->one();
if ($auth) {
if ($auth->delete()) {
\Yii::$app->session->setFlash(
'success',
'Successfully disconnect account'
);
} else {
\Yii::$app->session->setFlash(
'error',
'Failed to disconnect account: '.json_encode($auth->getErrors())
);
}
} else {
\Yii::$app->session->setFlash(
'error',
'Cannot find linked account.'
);
}
return $this->redirect(['users/link']);
Yii::$app->end();
} -
Create view users/link.php
Now add a menu to users/link if user is login in views/layout/main.php, or just simply type users/link in URL bar to go there. You can link/unlink your Google account there. Once you linked your Google account, you can use it to login.<?php use yii\helpers\Html; use yii\bootstrap\ActiveForm; use yii\authclient\widgets\AuthChoice; use app\models\Auth; $this->title = 'Link Your Account'; $this->params['breadcrumbs'][] = $this->title; ?> <div> <h1><?= Html::encode($this->title) ?></h1> <?php $user = \Yii::$app->user->identity; $authAuthChoice = yii\authclient\widgets\AuthChoice::begin([ 'baseAuthUrl' => ['site/auth'], 'popupMode' => false, ]) ?> <table class="table"> <?php foreach ($authAuthChoice->getClients() as $client): ?> <tr> <td style="width: 32px; vertical-align: middle"> <?= Html::tag('span', '', ['class' => 'auth-icon ' . $client->getName()]) ?> </td> <td style="vertical-align: middle"> <strong><?= $client->getTitle() ?></strong> </td> <td style="width: 120px"> <?php $auth = Auth::find()->where(['uid' => $user->id, 'source' => $client->id])->one(); ?> <?= $auth ? Html::a('Disconnect', ['/users/disconnect'], [ 'class' => 'btn btn-danger btn-block', 'data' => [ 'method' => 'post', 'params' => ['client' => $client->id], ], ]) : Html::a('Connect', ['/site/auth', 'authclient' => $client->id, 'return' => 'users/link'], [ 'class' => 'btn btn-success btn-block', ]) ?> </td> </tr> <?php endforeach; ?> </table> </div>
-
Register at https://developers.facebook.com/apps
- When you back in Dashboard, click Set Up on Facebook Login
- Select Web
- Go to Settings, Basic
- There you can obtain ID and Secret values to put into config.
-
Now you can add Facebook in authClientCollection in config/web.php
'components' => [ 'authClientCollection' => [ 'class' => 'yii\authclient\Collection', 'clients' => [ 'google' => [ 'class' => 'yii\authclient\clients\Google', 'clientId' => 'google_client_id', 'clientSecret' => 'google_client_secret', ],'facebook' => [], ] ],
'class' => 'yii\authclient\clients\Facebook',
'clientId' => 'facebook_app_id',
'clientSecret' => 'facebook_app_secret',
]
, - Under Products, Facebook Login, Settings, make sure you enable "Web OAuth Login" and specify "Valid OAuth Redirect URIs" as "https://example.com/auth?authclient=facebook"
You can download from github the completed code here: https://github.com/hensem/yii2_login
Credit:
https://code.tutsplus.com/tutorials/how-to-program-with-yii2-authclient-integration-with-twitter-google--cms-23489
https://github.com/yiisoft/yii2-authclient

















Comments
Post a Comment