博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Getting Started with AngularJS 1.5 and ES6: part4
阅读量:5832 次
发布时间:2019-06-18

本文共 12299 字,大约阅读时间需要 40 分钟。

  hot3.png

Authentication

In 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 use window.localStorage to store the JWT token, it is easy to read and restore authentication without new login.

Create a JWT service to wrap read and write localStorage actions.

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 /auth/login, /auth/signup for login and registration.

Logout is no need extra operation on server side. We are using stateless service, there is state need to clean.

Create an Auth service to wrap these operations.

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 attempAuth is responsive for signin and signup action, use a type to identify them.

The verifyAuth and ensureAuthIs are use for check user authentication status and make sure user is authenticated.

We have generated signin and signup component skeleton codes for this application.

Let's implements signin firstly.

signin.controller.js:

class SigninController {  constructor(Auth, $state, 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 method, when Auth.attempAuth is called successfully, then use angular-toastr to raise a notification and route to app.posts state.

signin.html:

{
{ $ctrl.name }}

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 ComponentsModule.

//...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:

{
{ $ctrl.name }}

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', [  commonSevices,  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 $httpProvider.

app.config.js

function jwtInterceptor(JWT, AppConstants, $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 requiresAuth property in state data to identify if a state should be authenticated.

Add the following code to app state.

$stateProvider    .state('app', {      abstract: true,      component: 'app',      data: {        requiresAuth: true      }    });

As described before, app is the root component of the component tree. Here we assume all component should be authenticated before route to it. But the data attribute can be inherited and overriden.

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 AppRun.

//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.

Unlike signup action, it is a simple link, signin and logout call controller methods: onSignin and onLogout.

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 $state to route the target state. We use Angular event publisher/subcribers to archive the purpose.

If you are writing the legacy Angular application, you could know well about the $scope.

In Angular $scopes are treeable, there is a $rootScope of an application, and all $scopes are inherited from it. Every $scope has a $parent property to access its parent scope, except $rootScope.

$scope has two methods to fire an event.

  • $scope.emit will fire an event up the scope.
  • $scope.broadcast will fire an event down scope.

$scope.on will observes events.

We use emit in our case, and we can use $rootScope to observe these events in AppRun.

$rootScope.$on("event:signinRequest", function (event, data) {    console.log("receviced:signinRequest");    $state.go('app.signin');  });  $rootScope.$on("event:logoutRequest", function (event, data) {    console.log("receviced:logoutRequest");    Auth.logout();    $state.go('app.signin');  });

In the navbar component, show-authed directive determines if show or hide button/links according to the authentication info.

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 .

Check the .

转载于:https://my.oschina.net/hantsy/blog/754526

你可能感兴趣的文章