Getting Started with AngularJS 1.5 and ES6: part4
AuthenticationIn a real world application,it should provide login,registration and logout features for users,and also can identify if a user has the roles or permissions to access the protected resources. I have set adding post and editing post requires authentication in backend APIs. And it uses a JWT token based authentication to authorize users. In this client,we Create a class JWT { constructor(AppConstants,$window) { 'ngInject'; this._AppConstants = AppConstants; this._$window = $window; } save(token) { this._$window.localStorage[this._AppConstants.jwtKey] = token; } get() { return this._$window.localStorage[this._AppConstants.jwtKey]; } destroy() { this._$window.localStorage.removeItem(this._AppConstants.jwtKey); } } export default JWT; The backend APIs provides Logout is no need extra operation on server side. We are using stateless service,there is state need to clean. Create an class Auth { constructor(JWT,AppConstants,$http,$state,$q) { 'ngInject'; this._JWT = JWT; this._AppConstants = AppConstants; this._$http = $http; this._$state = $state; this._$q = $q; this.current = null; } attempAuth(type,credentials) { let path = (type == 'signin') ? '/login' : '/signup'; let request = { url: this._AppConstants.api + '/auth' + path,method: 'POST',data: credentials }; return this._$http(request) .then((res) => { this._JWT.save(res.data.id_token); this.current = res.data.user; return res; }); } ensureAuthIs(b) { let deferred = this._$q.defer(); this.verifyAuth().then((authValid) => { // if it's the opposite,redirect home if (authValid !== b) { this._$state.go('app.signin'); deferred.resolve(false); } else { deferred.resolve(true); } }); return deferred.promise; } verifyAuth() { let deferred = this._$q.defer(); if (!this._JWT.get()) { deferred.resolve(false); return deferred.promise; } if (this.current) { deferred.resolve(true); } else { this._$http({ url: this._AppConstants.api + '/me',method: 'GET' }) .then( (res) => { this.current = res.data; deferred.resolve(true); },(err) => { this._JWT.destroy(); deferred.resolve(false); } ); } return deferred.promise; } logout() { this.current = null; this._JWT.destroy(); this._$state.go(this._$state.$current,null,{ refresh: true }); } } export default Auth; The The We have generated signin and signup component skeleton codes for this application. Let's implements signin firstly. signin.controller.js: class SigninController { constructor(Auth,toastr) { 'ngInject'; this._Auth = Auth; this._$state = $state; this._toastr = toastr; this.name = 'signin'; this.data = { username: '',password: '' }; } signin() { console.log("signin with credentials:" + this.data); this._Auth.attempAuth('signin',this.data) .then((res) => { this._toastr.success('Welcome back,' + this.data.username); this._$state.go('app.posts'); }); } } export default SigninController; In the signin.html: <div class="row"> <div class="offset-md-3 col-md-6"> <div class="card"> <div class="card-header"> <h1>{{ $ctrl.name }}</h1> </div> <div class="card-block"> <form id="form" name="form" class="form" ng-submit="$ctrl.signin()" novalidate> <div class="form-group" ng-class="{'has-danger':form.username.$invalid && !form.username.$pristine}"> <label class="form-control-label" for="username">{{'username'}}</label> <input class="form-control" id="username" name="username" ng-model="$ctrl.data.username" required/> <div class="form-control-feedback" ng-messages="form.username.$error" ng-if="form.username.$invalid && !form.username.$pristine"> <p ng-message="required">Username is required</p> </div> </div> <div class="form-group" ng-class="{'has-danger':form.password.$invalid && !form.password.$pristine}"> <label class="form-control-label" for="password">{{'password'}}</label> <input class="form-control" type="password" name="password" id="password" ng-model="$ctrl.data.password" required/> <div class="form-control-feedback" ng-messages="form.password.$error" ng-if="form.password.$invalid && !form.password.$pristine"> <p ng-message="required">Password is required</p> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-success btn-lg" ng-disabled="form.$invalid || form.$pending"> {{'SIGN IN'}} </div> </form> </div> <div class="card-footer"> Not registered,<a href="#" ui-sref="app.signup">{{'signup'}}</a> </div> </div> </div> </div> The signin is simple,username and password fields are rquired. Declare signin as an Angular module. /components/signin/index.js: import angular from 'angular'; import uiRouter from 'angular-ui-router'; import commonSevices from '../../common/services/'; import signinComponent from './signin.component'; let signinModule = angular.module('signin',[ commonSevices,uiRouter ]) .config(($stateProvider) => { "ngInject"; $stateProvider .state('app.signin',{ url: '/signin',component: 'signin' }); }) .component('signin',signinComponent) .name; export default signinModule; Add signinModule as a dependency of //... import Signin from './signin/'; //... let componentsModule = angular.module('app.components',[ //... Signin,//... ]) .name; Similarly,create signup component. signup.controller.js: class SignupController { constructor(Auth,$state) { 'ngInject'; this._Auth = Auth; this._$state = $state; this.name = 'signup'; this.data = { firstName: '',lastName: '',username: '',password: '' }; } signup() { console.log('sign up with data @' + this.data); this._Auth.attempAuth('signup',this.data) .then((res) => { this._$state.go('app.posts'); }); } } export default SignupController; signup.html: <div class="row"> <div class="offset-md-3 col-md-6"> <div class="card"> <div class="card-header"> <h1>{{ $ctrl.name }}</h1> </div> <div class="card-block"> <form id="form" name="form" class="form" ng-submit="$ctrl.signup()" novalidate> <div class="row"> <div class="col-md-6"> <div class="form-group" ng-class="{'has-danger':form.firstName.$invalid && !form.firstName.$pristine}"> <label class="form-control-label" for="firstName">{{'firstName'}}</label> <input class="form-control" id="firstName" name="firstName" ng-model="$ctrl.data.firstName" required/> <div class="form-control-feedback" ng-messages="form.firstName.$error" ng-if="form.firstName.$invalid && !form.firstName.$pristine"> <p ng-message="required">FirstName is required</p> </div> </div> </div> <div class="col-md-6"> <div class="form-group" ng-class="{'has-danger':form.lastName.$invalid && !form.lastName.$pristine}"> <label class="form-control-label col-md-12" for="lastName">{{'lastName'}}</label> <input class="form-control" id="lastName" name="lastName" ng-model="$ctrl.data.lastName" required/> <div class="form-control-feedback" ng-messages="form.lastName.$error" ng-if="form.lastName.$invalid && !form.lastName.$pristine"> <p ng-message="required">LastName is required</p> </div> </div> </div> </div> <div class="form-group" ng-class="{'has-danger':form.username.$invalid && !form.username.$pristine}"> <label class="form-control-label" for="username">{{'username'}}</label> <input class="form-control" id="username" name="username" ng-model="$ctrl.data.username" required ng-minlength="6" ng-maxlength="20" /> <div class="form-control-feedback" ng-messages="form.username.$error" ng-if="form.username.$invalid && !form.username.$pristine"> <p ng-message="required">Username is required</p> <p ng-message="minlength">Username is too short(at least 6 chars)</p> <p ng-message="maxlength">Username is too long(at most 20 chars)</p> </div> </div> <div class="form-group" ng-class="{'has-danger':form.password.$invalid && !form.password.$pristine}"> <label class="form-control-label" for="password">{{'password'}}</label> <input class="form-control" type="password" name="password" id="password" ng-model="$ctrl.data.password" required ng-minlength="6" ng-maxlength="20" /> <div class="form-control-feedback" ng-messages="form.password.$error" ng-if="form.password.$invalid && !form.password.$pristine"> <p ng-message="required">Password is required.</p> <p ng-message="minlength,maxlength">Password should be consist of 6 to 20 chars.</p> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-success btn-lg" ng-disabled="form.$invalid || form.$pending"> {{'SIGN UP'}} </div> </form> </div> <div class="card-footer"> Already registered,<a href="#" ui-sref="app.signin">{{'signin'}}</a> </div> </div> </div> </div> It is similar with sginin template file,we add two more fields,FirstName and LastName,and for password and username fields we have add more validation rules. Declare the signup Angular Module. import angular from 'angular'; import uiRouter from 'angular-ui-router'; import commonSevices from '../../common/services/'; import signupComponent from './signup.component'; let signupModule = angular.module('signup',uiRouter ]) .config(($stateProvider) => { "ngInject"; $stateProvider .state('app.signup',{ url: '/signup',component: 'signup',data: { requiresAuth: false } }); }) .component('signup',signupComponent) .name; export default signupModule; Add signup module to componentsModule dependencies. import Signup from './signup/'; //... let componentsModule = angular.module('app.components',[ //... Signup,//... ]) .name; Add an intecepter to app.config.js function jwtInterceptor(JWT,$window,$q) { 'ngInject'; return { // automatically attach Authorization header request: function (config) { if (/*config.url.indexOf(AppConstants.api) === 0 &&*/ JWT.get()) { config.headers.Authorization = 'Bearer ' + JWT.get(); } return config; },// Handle 401 responseError: function (rejection) { if (rejection.status === 401) { // clear any JWT token being stored JWT.destroy(); // do a hard page refresh $window.location.reload(); } return $q.reject(rejection); } }; } //...in AppConfig function $httpProvider.interceptors.push(jwtInterceptor); Now,sginin and signup should work. Next,we will try to protect the pages requires authentication,such as new-post and edit-post. In the state definition,add a Add the following code to $stateProvider .state('app',{ abstract: true,component: 'app',data: { requiresAuth: true } }); As described before, Add the following codes to posts,post-details,signin,signup state definitions. data: { requiresAuth: true } It tell these states are not required to be authenticated. Finally,observes the state change event in //processing auth redirecting $transitions.onStart({ to: (state) => { return !!state.data.requiresAuth; } },function (trans) { var $state = trans.router.stateService; var _Auth = trans.injector().get('Auth'); _Auth.ensureAuthIs(true); }); Now try to click new-post link in the navbar when you are not authenticated,it will redirect to signin page. Add signin,signup and logout button/links in navbar.html. <ul class="nav navbar-nav pull-md-right"> <li class="nav-item" show-authed="false"><button class="btn btn-outline-success" ng-click="$ctrl.onSignin()">{{'signin'}}</button></li> <li class="nav-item" show-authed="false"><a class="nav-link" href="#" ui-sref="app.signup">{{'signup'}}</a></span> </li> <li class="nav-item" show-authed="true"><button type="button" class="btn btn-outline-danger" ng-click="$ctrl.onLogout()">{{'logout'}}</button></span> </li> </ul> Unlike signup action,it is a simple link,signin and logout call controller methods: class NavbarController { constructor($scope) { 'ngInject'; this._$scope = $scope; this.name = 'navbar'; } $onInit() { console.log("initializing NavbarController..."); } $onDestroy() { console.log("destroying NavbarController..."); } onSignin() { console.log("on signin..."); this._$scope.$emit("event:signinRequest"); } onLogout() { console.log("on logout..."); this._$scope.$emit("event:logoutRequest"); } } export default NavbarController; In these methods,we also do not use If you are writing the legacy Angular application,you could know well about the In Angular $scopes are treeable,there is a
We use $rootScope.$on("event:signinRequest",function (event,data) { console.log("receviced:signinRequest"); $state.go('app.signin'); }); $rootScope.$on("event:logoutRequest",data) { console.log("receviced:logoutRequest"); Auth.logout(); $state.go('app.signin'); }); In the navbar component, Have a look at the show-authed.directive.js under common/diretives/ folder. function ShowAuthed(Auth) { 'ngInject'; return { restrict: 'A',link: function(scope,element,attrs) { scope.Auth = Auth; scope.$watch('Auth.current',function(val) { // If user detected if (val) { if (attrs.showAuthed === 'true') { element.css({ display: 'inherit'}) } else { element.css({ display: 'none'}) } // no user detected } else { if (attrs.showAuthed === 'true') { element.css({ display: 'none'}) } else { element.css({ display: 'inherit'}) } } }); } }; } export default ShowAuthed; Also do not forget to register it in the directivesModule,and set directivesModule as a dependency of common module. common/diretives/index.js: import angular from 'angular'; import ShowAuthed from './show-authed.directive'; let directivesModule = angular.module('app.common.directives',[]) .directive('showAuthed',ShowAuthed) .name; export default directivesModule; common/index.js: //... import commonDirectivesModule from './directives'; let commonModule = angular.module('app.common',[ //... commonDirectivesModule ]) //... In this sample,only includes a simple authentication,if you need more complex and fine-grained control of authorization,read this stackoverflow discussion for more details. Check the sample codes. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |