Sử dụng Cookies and Tokens xác thực yêu cầu trong AngularJS

Trong bài viết này sẽ giới thiệu về Token-Based sử dụng trong xác thực của ứng dụng AngularJS

Về cơ bản có 2 cách xác thực ở phía server cho các ứng dụng frontend và API

  • Sử dụng cookie-Based: Là các tập tin token được sử dụng bên server để xác thực trên mỗi yêu cầu từ phía client

  • Sử dụng Token-Based: Dựa vào một đoạn mã đã được đăng ký và gửi đến server để xác nhận trên mỗi yêu cầu từ phía client

Mô hình hoạt động: 68747470733a2f2f646c2e64726f70626f7875736572636f6e74656e742e636f6d2f752f32313636353130352f636f6f6b69652d746f6b656e2d617574682e706e67.png

Trong bài viết này sẽ nói về phương pháp sử dụng Token-Based, những lợi ích khi sử dụng phương pháp này

  • Cross-domain/CORS: Cookies and CORS không thực hiện tốt, nếu dùng Token-Based cho phép thực hiện yêu cầu AJAX đến bất kỳ 1 máy chủ nào, trên mọi tên miền vì bạn sử đụng HTTP để thực hiện gửi yêu cầu

  • Stateless(Mở rộng máy chủ)

  • CDN: bạn có thể phục vụ tất cả tài nguyên cho ứng dụng của bạn từ 1 máy CDN (ví dụ như: javascript, HTML, hình ảnh, vv) về phía máy chủ của bạn chỉ là các API

  • Decoupling: phải có cơ chế xác thực đặc biệt vì các mã xác thực có thể tạo ra ở bất kỳ nơi nào. Vì thế API của bạn có chỉ được gọi từ một nơi duy nhất có chứng thực cho các yêu cầu

  • Mobile: Khi bạn bắt đầu làm việc trên nền tảng di động(iOS, Android, Window8, vv) các cookies không còn là lý tưởng đảm bảo an toàn cho các API(bạn phải lưu trữ các cookies). Áp dụng phương pháp Token-Based thì sẽ đơn giản hơn rất nhiều.

  • CSRF: Kể từ khi bạn không sử dụng cookie thì bạn không cần phải bảo vệ chống lại CSRF

  • Standard-based: API của bạn có thể xây dựng dựa trên tiêu chuẩn JSON Web Token(JWT). Đây là chuẩn có nhiều thư viện hỗ trợ (.NET, Ruby, Java, Python, PHP)

** JSON Web Token là gì? là một thể định đạng làm việc giống như header trong giao thức HTTP. JWT là kiến trúc là giải pháp để chuyển yêu cầu giữa các client và server an toàn **

Cấu trúc của một token JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo

Giờ chúng ta sẽ tiến hành cài đặt:

Phần server side:

Ở đây sẽ hướng dẫn xây dựng một ứng dụng Node.js, bạn có thể thấy được các thành phần của kiến trúc này

Cài đặt express-jwt và jsonwebtoken

$ npm install express-jwt jsonwebtoken

Cấu hình express middleware bảo vệ cho phép gọi tới /api

var expressJwt = require('express-jwt');
var jwt = require('jsonwebtoken');

// We are going to protect /api routes with JWT
app.use('/api', expressJwt({secret: secret}));

app.use(express.json());
app.use(express.urlencoded());

Ứng dụng angular sẽ thực hiện post thông tin người sử dụng thông qua AJAX

app.post('/authenticate', function (req, res) {
  //TODO validate req.body.username and req.body.password
  //if is invalid, return 401
  if (!(req.body.username === 'ngongoc' && req.body.password === '123456')) {
    res.send(401, 'Username or password sai!');
    return;
  }

  var profile = {
    first_name: 'Ngoc',
    last_name: 'Ngo',
    email: '[email protected]',
    id: 123
  };

  // Gửi thông tin profile thông qua token
  var token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 });

  res.json({ token: token });
});

Sau khi đã đăng nhập và nhận được token từ server, tới lần gửi yêu cầu kế tiếp sẽ được kiểm tra bởi expressJwt Middleware: /api/restricted

app.get('/api/restricted', function (req, res) {
  console.log('user ' + req.user.email + ' is calling /api/restricted');
  res.json({
    name: 'viblo'
  });
});

Ứng dụng AngularJS

Khách hàng sẽ sử dụng AngularJS để lấy JWTToken, ở phía người client sẽ gửi thông tin về người dùng lên server

<div ng-app="myApp">
  <div class="page-header">Login Form</div>
  <div ng-controller="UserCtrl">
    <form ng-submit="submit()" class="form-horizontal">
      <div class="form-group">
        <label class="control-label col-md-2">Username:</label>
        <div class="col-md-2">
          <input ng-model="user.username" type="text" name="user" placeholder="Username" class="form-control" />
        </div>
      </div>
      <div class="form-group">
        <label class="control-label col-md-2">Password:</label>
        <div class="col-md-2">
          <input ng-model="user.password" type="text" name="password" placeholder="Password" class="form-control" />
        </div>
      </div>
      <div class="form-group">
        <div class="col-md-6 col-md-offset-2">
          <span class="label label-danger">{{message}}</span>
        </div>
      </div>
      <div class="form-group">
        <div class="col-md-2 col-md-offset-2">
          <button type="submit" class="btn btn-success">Login</button>
        </div>
      </div>
    </form>
  </div>
</div>

Tạo một Angular Controller để xử lý

var myApp = angular.module("myApp", []);

myApp.controller('UserCtrl', function ($scope, $http, $window) {
  $scope.user = {username: 'ngongoc', password: '123456'};
  $scope.message = '';
  $scope.submit = function () {
    $http
      .post('/authenticate', $scope.user)
      .success(function (data, status, headers, config) {
        $window.sessionStorage.token = data.token;
        $scope.message = 'Welcome';
      })
      .error(function (data, status, headers, config) {
        // Erase the token if the user fails to log in
        delete $window.sessionStorage.token;

        // Handle login errors here
        $scope.message = 'Error: Invalid user or password';
      });
  };
});

Sau khi thực hiện gửi yêu cầu đến server thành công, nhận được chuỗi JWT và sẽ được lưu trên sessionStorage. Sau khi token đã được lưu, chúng ta sẽ xây dựng việc xác nhận ở trên mỗi request $http như là một phần của header bằng cách sử dụng Bearer Token.

sessionStorage: không hỗ trợ hết tất cả các trình duyệt(bạn có thể sử dụng polyfill), bạn có thể quan tâm đến $cookies, $cookieStore và localStorage(Dữ liệu sẽ bị xoá khi Tab bị đóng)

myApp.factory('authInterceptor', function ($rootScope, $q, $window) {
  return {
    request: function (config) {
      config.headers = config.headers || {};
      if ($window.sessionStorage.token) {
        config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
      }
      return config;
    },
    response: function (response) {
      if (response.status === 401) {
        // Xử lý khi không thể chứng thực được yêu cầu
      }
      return response || $q.when(response);
    }
  };
});

myApp.config(function ($httpProvider) {
  $httpProvider.interceptors.push('authInterceptor');
});

Bài viết kế tiếp, tôi sẽ viết về chủ đề

  • Làm thế nào để AngularJS xử lý xác thực với các mạng xã hội: Facebook, twitter...
  • Làm thế nào để xứ lý kết thúc session

**Tài liệu tham khảo **