HTTP Basic Authentication AngularJS

Bài viết này là một ví dụ về trang login với HTTP Basic Authentication sử dụng AngularJS và nó sẽ lưu trạng thái đăng nhập của người dùng sau khi refresh trang.

Prerequisite

  • BrowserSync là một npm package nó cần cài đặt trước Node.js trước. Để install BrowserSync mở terminal và gõ: npm install -g browser-sync.
  • Bower để quản lý các framworks, libraries, assets và utilities. npm install -g bower

Nếu lúc cài đặt bị lỗi permission hãy thêm "sudo" trước câu lệnh cài đặt.

Setup

Tạo một thư mục project

  • mkdir angularjs-basic-http-authentication && cd angularjs-basic-http-authentication

Tải các thư viện để áp dụng vào project

  • bower init (điền các thông tin cần thiết)
  • bower install bootstrap --save
  • bower install angular --save
  • bower install angular-route --save
  • bower install angular-cookies --save
angularjs-basic-http-authentication
  - index.html
  /angular
    /scripts
      - app.js
      - authentication_service.js
      - home_controller.js
      - login_controller.js
   /views
     - home.html
      - login.html
  /images
    - loading.gif
  /bower_components

Coding

Theo cấu trúc ở trên thì bắt đầu viết code cho từng file một.

index.html

/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>AngularJS Basic HTTP Authentication</title>
  <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css" />
  <script src="bower_components/jquery/dist/jquery.slim.min.js"></script>
  <script src="bower_components/tether/dist/js/tether.min.js"></script>
  <script src="bower_components/angular/angular.min.js"></script>
  <script src="bower_components/angular-cookies/angular-cookies.min.js"></script>
  <script src="bower_components/angular-route/angular-route.min.js"></script>
  <script src="angular/scripts/app.js"></script>
  <script src="angular/scripts/authentication_service.js"></script>
  <script src="angular/scripts/login_controller.js"></script>
  <script src="angular/scripts/home_controller.js"></script>
</head>
<body ng-app="BasicHttpAuthApp">
  <div class="jumbotron">
    <div class="container">
      <div class="col-xs-offset-2 col-xs-8">
        <div ng-view></div>
      </div>
    </div>
  </div>
</body>
</html>

Home view

hiển thị text báo đến user đã đăng nhập thành công.

/angular/views/home.html
<h1>Home</h1>
<p>You're logged in!!</p>
<p><a href="#!/login">Logout</a></a></p>

Login view

gồm các form login với các inputs và các validation messages.

/angular/views/login.html
<div ng-show="error" class="alert alert-danger">{{error}}</div>
<form name="form" ng-submit="login()" role="form">
  <div class="form-group">
    <label for="username">Username</label>
    <i class="fa fa-key"></i>
    <input type="text" name="username" id="username" class="form-control" ng-model="username" required />
    <span ng-show="form.username.$dirty && form.username.$error.required" class="help-block">Username is required</span>
  </div>
  <div class="form-group">
    <label for="password">Password</label>
    <i class="fa fa-lock"></i>
    <input type="password" name="password" id="password" class="form-control" ng-model="password" required />
    <span ng-show="form.password.$dirty && form.password.$error.required" class="help-block">Password is required</span>
  </div>
  <div class="form-actions">
    <button type="submit" ng-disabled="form.$invalid || dataLoading" class="btn btn-danger">Login</button>
    <img ng-if="dataLoading" src="images/loading.gif"/>
  </div>
</form>

AngularJS App

file app.js là root cửa ứng dụng AngularJS, xác định tên app, các dependencies, routes và cầu hình startup. Phương thức run() ở đây để khởi tạo biến currentUser từ một cookie để giữ người dùng đã đăng nhập khi reload trang, nó cũng chứa các event handler để kiểm tra xem người dùng đã đăng nhập hay chưa trước khi route thay đổi.

/scripts/app.js
"use strict";

var module_authen = angular.module("Authentication", []);
var module_home = angular.module("Home", []);
var basicHttpAuthApp = angular.module("BasicHttpAuthApp", ["Authentication", "Home", "ngRoute", "ngCookies"]);

basicHttpAuthApp.config(["$routeProvider", function ($routeProvider) {
  $routeProvider
    .when("/login", {
      controller: "LoginController",
      templateUrl: "angular/views/login.html"
    })
    .when("/", {
      controller: "HomeController",
      templateUrl: "angular/views/home.html"
    })
    .otherwise({ redirectTo: "/login" });
}])

basicHttpAuthApp.run(["$rootScope", "$location", "$cookieStore", "$http",
  function ($rootScope, $location, $cookieStore, $http) {
    $rootScope.globals = $cookieStore.get("globals") || {};
    if ($rootScope.globals.currentUser) {
      $http.defaults.headers.common["Authorization"] = "Basic " + $rootScope.globals.currentUser.authdata; // jshint ignore:line
    }

    $rootScope.$on("$locationChangeStart", function (event, next, current) {
      if ($location.path() !== "/login" && !$rootScope.globals.currentUser) {
        $location.path("/login");
      }
    });
  }
]);

Login Controller

chứa phương thức login() sử dụng Authentication Service để validate username và password nhập trong màn hình login.

/scripts/login_controller.js
"use strict";

module_authen.controller("LoginController", ["$scope", "$rootScope", "$location", "AuthenticationService",
  function ($scope, $rootScope, $location, AuthenticationService) {
    AuthenticationService.ClearCredentials();

    $scope.login = function () {
      $scope.dataLoading = true;
      AuthenticationService.Login($scope.username, $scope.password, function(response) {
        if(response.success) {
          AuthenticationService.SetCredentials($scope.username, $scope.password);
          $location.path("/");
        } else {
          $scope.error = response.message;
          $scope.dataLoading = false;
        }
      });
    };
  }]);

Home Controller

phần này chỉ hiển thị text nên chưa có logic để xử lý

/scripts/home_controller.js
"use strict";

module_home.controller("HomeController", ["$scope", function ($scope) {

}]);

Authentication Service

đóng vài trò là interface giữa angularjs app và server side api, vậy các http requests sẽ quản lý ở đây. Nó chứa một service Base64 để mã hóa username và password để dùng cho HTTP Authorization header cho tất cả request sau khi đăng nhập.

/scripts/authentication_service.js
"use strict";

module_authen.factory("AuthenticationService",
  ["Base64", "$http", "$cookieStore", "$rootScope", "$timeout",
  function (Base64, $http, $cookieStore, $rootScope, $timeout) {
    var service = {};
    service.Login = function (username, password, callback) {
      $timeout(function(){
        var response = { success: username === "framgia" && password === "test123" };
        if(!response.success) {
          response.message = "Username or password is incorrect";
        }
        callback(response);
      }, 1000);
    };

    service.SetCredentials = function (username, password) {
      var authdata = Base64.encode(username + ":" + password);
      $rootScope.globals = {
        currentUser: { username: username, authdata: authdata }
      };

      $http.defaults.headers.common["Authorization"] = "Basic " + authdata;
      $cookieStore.put("globals", $rootScope.globals);
    };

    service.ClearCredentials = function () {
      $rootScope.globals = {};
      $cookieStore.remove("globals");
      $http.defaults.headers.common.Authorization = "Basic ";
    };

    return service;
  }]);

module_authen.factory("Base64", function () {
  var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  return {
    encode: function (input) {
      var output = "";
      var chr1, chr2, chr3 = "";
      var enc1, enc2, enc3, enc4 = "";
      var i = 0;
      do {
        chr1 = input.charCodeAt(i++);
        chr2 = input.charCodeAt(i++);
        chr3 = input.charCodeAt(i++);

        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;

        if (isNaN(chr2)) {
          enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
          enc4 = 64;
        }

        output = output +
          keyStr.charAt(enc1) +
          keyStr.charAt(enc2) +
          keyStr.charAt(enc3) +
          keyStr.charAt(enc4);
        chr1 = chr2 = chr3 = "";
        enc1 = enc2 = enc3 = enc4 = "";
      } while (i < input.length);

      return output;
    },

    decode: function (input) {
      var output = "";
      var chr1, chr2, chr3 = "";
      var enc1, enc2, enc3, enc4 = "";
      var i = 0;

      var base64test = /[^A-Za-z0-9\+\/\=]/g;
      if (base64test.exec(input)) {
        window.alert("There were invalid base64 characters in the input text.\n" +
          "Valid base64 characters are A-Z, a-z, 0-9, "+", "/",and '='\n" +
          "Expect errors in decoding.");
      }
      input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

      do {
        enc1 = keyStr.indexOf(input.charAt(i++));
        enc2 = keyStr.indexOf(input.charAt(i++));
        enc3 = keyStr.indexOf(input.charAt(i++));
        enc4 = keyStr.indexOf(input.charAt(i++));

        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;

        output = output + String.fromCharCode(chr1);

        if (enc3 != 64) {
          output = output + String.fromCharCode(chr2);
        }
        if (enc4 != 64) {
          output = output + String.fromCharCode(chr3);
        }

        chr1 = chr2 = chr3 = "";
        enc1 = enc2 = enc3 = enc4 = "";

      } while (i < input.length);

      return output;
    }
  };
});

Demo

Để chạy server gõ command browser-sync start --server --files "*.html" trong thư mục của project.

Source code

References