on
Italy
- Get link
- X
- Other Apps
php yii migrate command. You can add a demo user with this SQL query if you need to.INSERT INTO `user` (`id`, `username`, `auth_key`, `password_hash`, `password_reset_token`, `email`, `status`, `created_at`, `updated_at`) VALUES (1, 'demo', 'u4qnlunMrSWqcyitTV06gH5C8ZlAaWar', '$2y$13$dN9ipH0Pc2zLBsDGfIkLOuZDvG0Lv5YACMWCAUIYeCHqNKfw3VbDa', NULL, 'demo@localhost.com', 10, 1428424049, 1428424049);To make authentication work we need to implement the findIdentityByAccessToken() function if common/models/User.php. To make this tutorial simple we will use the auth_key field from the user table, provided by Yii2, as the acces token for our user.
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['auth_key' => $token]);
}
Now let’s configure several components in frontend/config/main.php.
We won’t be using cookies for authentication, so we will disable ‘enableSession’ => false,
for the ‘user’ component. We also wan’t to receive a 401 response
instead of a redirect to the login action so our Angular app would know
that access was denied ‘loginUrl’ => null.'user' => [ 'identityClass' => 'common\models\User', 'enableSession' => false, 'loginUrl' => null, ],We will also configure a JsonParser for the Request component, because we will be receiving form data as JSON.
'request' => [ 'class' => '\yii\web\Request', 'enableCookieValidation' => false, 'parsers' => [ 'application/json' => 'yii\web\JsonParser', ], ],
'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, ],
Authorization: Bearer u4qnlunMrSWqcyitTV06gH5C8ZlAaWar.$behaviors = parent::behaviors(); $behaviors['authenticator'] = [ 'class' => HttpBearerAuth::className(), 'only' => ['dashboard'], ];Note that we want to keep the behaviors() configuration from yii\rest\Controller which is why we preserve it in the first line $behaviors = parent::behaviors(); and keep adding to it.
$behaviors['contentNegotiator'] = [ 'class' => ContentNegotiator::className(), 'formats' => [ 'application/json' => Response::FORMAT_JSON, ], ];We also wan’t to allow access to the dashboard only for authenticated users.
$behaviors['access'] = [ 'class' => AccessControl::className(), 'only' => ['dashboard'], 'rules' => [ [ 'actions' => ['dashboard'], 'allow' => true, 'roles' => ['@'], ], ], ];Finally we can return our behavior configuration return $behaviors;
public function actionLogin()
{
$model = new LoginForm();
if ($model->load(Yii::$app->getRequest()->getBodyParams(), '') && $model->login()) {
return ['access_token' => Yii::$app->user->identity->getAuthKey()];
} else {
$model->validate();
return $model;
}
}
Our dashboard is protected by AccessControl so we only need to provide the data for the view in our dashboard action.public function actionDashboard()
{
$response = [
'username' => Yii::$app->user->identity->username,
'access_token' => Yii::$app->user->identity->getAuthKey(),
];
return $response;
}
To make the Flash in the contact view we will send the content and the class of the flash. Here is our contact action.public function actionContact()
{
$model = new ContactForm();
if ($model->load(Yii::$app->getRequest()->getBodyParams(), '') && $model->validate()) {
if ($model->sendEmail(Yii::$app->params['adminEmail'])) {
$response = [
'flash' => [
'class' => 'success',
'message' => 'Thank you for contacting us. We will respond to you as soon as possible.',
]
];
} else {
$response = [
'flash' => [
'class' => 'error',
'message' => 'There was an error sending email.',
]
];
}
return $response;
} else {
$model->validate();
return $model;
}
}
That’s it for our API controller.var app = angular.module('app', [
'ngRoute', //$routeProvider
'mgcrea.ngStrap', //bs-navbar, data-match-route directives
'controllers' //Our module frontend/web/js/controllers.js
]);
We need to tell the app which view corresponds to which controller.app.config(['$routeProvider', '$httpProvider',
function($routeProvider, $httpProvider) {
$routeProvider.
when('/', {
templateUrl: 'partials/index.html',
}).
when('/about', {
templateUrl: 'partials/about.html'
}).
when('/contact', {
templateUrl: 'partials/contact.html',
controller: 'ContactController'
}).
when('/login', {
templateUrl: 'partials/login.html',
controller: 'LoginController'
}).
when('/dashboard', {
templateUrl: 'partials/dashboard.html',
controller: 'DashboardController'
}).
otherwise({
templateUrl: 'partials/404.html'
});
$httpProvider.interceptors.push('authInterceptor');
}
]);
We also pushed an interceptor called authInterceptor. It will add the
access_token to the users requests if the user is logged in. And
redirect to the login form in case of a “401 Unauthorized” HTTP status.app.factory('authInterceptor', function ($q, $window, $location) {
return {
request: function (config) {
if ($window.sessionStorage.access_token) {
//HttpBearerAuth
config.headers.Authorization = 'Bearer ' + $window.sessionStorage.access_token;
}
return config;
},
responseError: function (rejection) {
if (rejection.status === 401) {
$location.path('/login').replace();
}
return $q.reject(rejection);
}
};
});
var controllers = angular.module('controllers', []);
controllers.controller('MainController', ['$scope', '$location', '$window',
function ($scope, $location, $window) {
$scope.loggedIn = function() {
return Boolean($window.sessionStorage.access_token);
};
$scope.logout = function () {
delete $window.sessionStorage.access_token;
$location.path('/login').replace();
};
}
]);
The DashboardController will be very simple. It will request data from ‘api/dashboard’ and push the data into the view.controllers.controller('DashboardController', ['$scope', '$http',
function ($scope, $http) {
$http.get('api/dashboard').success(function (data) {
$scope.dashboard = data;
})
}
]);
The LoginController will have one function login(), that will handle
the ng-submit event for the login form. The function makes a POST
request to ‘api/login’ and sends the username and password. If the
request is successful the received session_token is stored and the user
is redirected to the dashboard. In case there is an error (the form data
is invalid, or the user doesn’t exist) the error data is pushed into
the view, where it will be displayed to the user. Here’s how the error
data looks when an empty form is submitted.[{"field":"username","message":"Username cannot be blank."},{"field":"password","message":"Password cannot be blank."}]
Here’s the code for the LoginController.controllers.controller('LoginController', ['$scope', '$http', '$window', '$location',
function($scope, $http, $window, $location) {
$scope.login = function () {
$scope.submitted = true;
$scope.error = {};
$http.post('api/login', $scope.userModel).success(
function (data) {
$window.sessionStorage.access_token = data.access_token;
$location.path('/dashboard').replace();
}).error(
function (data) {
angular.forEach(data, function (error) {
$scope.error[error.field] = error.message;
});
}
);
};
}
]);
The ContactController will be the biggest one in our module. It will
have two functions. refreshCaptcha() will handle the ng-click event for
the captcha image. It will make a GET request
to ‘site/captcha?refresh=1’ to get a different captcha if the currently
provided one is not readable. The other function contact() will handle
the ng-submit event for the contact form. It will POST the form data
to ‘api/contact’ and in case of success it will push the “flash” data to
the view. After that the form will be cleared and the captcha will be
refreshed. In case of an error it will push the error data to the view.{{ error['username'] }}
The other part that’s different about the contact form is the captcha image. It has a ng-click event and the source for the image is provided by the captchUrl variable.{{flash.message}}
public $js = [ 'js/app.js', 'js/controllers.js', ];
Comments
Post a Comment