文档章节

Getting Started with AngularJS 1.5 and ES6: part4

hantsy
 hantsy
发布于 2016/10/05 09:11
字数 1753
阅读 14
收藏 0

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:

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

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

  <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: 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 this stackoverflow discussion for more details.

Check the sample codes.

© 著作权归作者所有

hantsy
粉丝 113
博文 88
码字总数 78079
作品 0
浦东
程序员
私信 提问
什么时候应该使用 Angular 2

人们常常会问--“我应该在我的新项目中要使用 Angular 2吗?” 假使该项目不需要被准备好几个月? 假使它是一个小项目,或者一个概念验证? 可能你已经在家中正在玩弄Angular 2,但是在工作中...

oschina
2016/01/08
8.6K
13
linux 环境下 angular2 生成component 报错 ELOOP:too many symbolic links encountered, stat

adaptercat@debian :/project/angular/AngularTest/src/app$ ng generate component t Error: ELOOP: too many symbolic links encountered, stat '/project/angular/AngularTest/node_modul......

adaptercat
2017/10/10
445
1
ngular2 VS Angular4 深度对比:特性、性能

在Web应用开发领域,Angular被认为是最好的开源JavaScript框架之一。 Google的Angular团队已于3月23日发布了Angular4,而期待已久的Angular2版本则是之前版本的完全重构。 对于成熟的开发人员...

机器的心脏
2018/06/02
0
0
AngularJS2.0 教程系列(一)

Why Angular2 Angular1.x显然非常成功,那么,为什么要剧烈地转向Angular2? 性能的限制 AngularJS当初是提供给设计人员用来快速构建HTML表单的一个内部工具。随着时间的推移,各种特性 被加...

笔阁
2015/07/22
24.5K
1
Angular 1 和 Angular 2 集成:无缝升级的方法

已经有了 Angular 1 应用程序,正在想着怎么把它升级到 Angular 2?看看我们是怎么样支持递增升级的。 摘要 好消息。 我们计划在同一应用程序上允许 Angular 1 和 Angular 2 混合使用 你可以...

oschina
2015/08/27
9K
13

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周一乱弹 —— 年迈渔夫遭黑帮袭抢

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @tom_tdhzz :#今日歌曲推荐# 分享Elvis Presley的单曲《White Christmas》: 《White Christmas》- Elvis Presley 手机党少年们想听歌,请使劲...

小小编辑
今天
1K
20
CentOS7.6中安装使用fcitx框架

内容目录 一、为什么要使用fcitx?二、安装fcitx框架三、安装搜狗输入法 一、为什么要使用fcitx? Gnome3桌面自带的输入法框架为ibus,而在使用ibus时会时不时出现卡顿无法输入的现象。 搜狗和...

技术训练营
昨天
5
0
《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
昨天
9
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
昨天
17
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部