AUTHENTICATING REACTJS APP WITH DEVISE GEM

Hôm nay tôi xin giới thiệu tới các bạn sử dụng gem devise để xác thực một ứng dụng sử dụng React.

Trong bài viết này tôi sẽ xây dựng một ứng dụng Rails API để phục vụ cho ứng dụng React. ReactJS xử lý tất cả các routes không được cung cấp bởi Rails. Với thiết lập này, ReactJS luôn luôn có quyền truy cập vào đúng CSRF thẻ mà nó có thể lấy từ head của HTML document.

Trong bài viết này tôi sẽ không bàn tới việc cài đặt gem devise cơ bản và sẽ sử dụng ngay trong phần dưới đây. Các bạn có thể tham khảo cài đặt và sử dụng tại document của gem devise. Cuối cùng, tất cả các file trong các ví dụ dưới đây sẽ được liên quan đến ứng dụng rails.

Giờ chúng ta sẽ bắt đầu vào nội dung chính.

Một vài Rails Stuff cần thiết

Bước 1: Devise nhận json requests

Bằng cách thêm đoạn code sau vào file config/application.rb:

#config/application.rb

[...]
config.to_prepare do
  DeviseController.respond_to :html, :json
end
[...]

Bước 2: Thực hiện một số thay đổi nhỏ trong app/controllers/application_controller.rb

Chúng ta sẽ sửa "protect_from_forgery with: :exception" thành như sau:

#app/controllers/application_controller.rb

[...]
protect_from_forgery with: :null_session

before_action :configure_permitted_parameters, if: :devise_controller?
[...]

Thêm đoạn code sau để đảm bảo rằng devise cho phép thiết lập một vài thông số như sau:

#app/controllers/application_controller.rb

[...]
private
def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_up) do |user_params|
    user_params.permit :name, :provider, :uid
  end
end
[...]

Bước 3: Tạo một controller mới tên auth_controller.rb

Trong controller này sẽ tạo một method trả về false nếu như user chưa đăng nhập và dữ liệu nếu đã đăng nhập thành công:

#app/controllers/auth_controller.rb

class AuthController < ApplicationController
  def is_signed_in?
    if user_signed_in?
      render json: {signed_in: true, user: current_user}
    else
      render json: {signed_in: false}
    end
  end
end

Thêm một route mới:

#config/routes.rb

[...]
scope :auth do
  get "is_signed_in", to: "auth#is_signed_in?"
end
[...]

Một vài React Stuff cần thiết

Chúng ta sẽ đặt ra một số mục tiêu sau đây:

  1. Để có một cách để xác định xem người dùng hiện tại được SignedIn từ App ReactJs.
  2. Để chắc chắn rằng chúng ta có thể truy cập và gửi đúng CSRF mã thông báo với mỗi request.
  3. Để có thể nhập từ App ReactJs.
  4. Để có thể Đăng xuất khỏi App ReactJs
  5. Để có thể đăng ký từ App ReactJs

Bước 1: Kết nối ứng dụng của bạn tới “is_signed_in” endpoint

Trong bài viết này điểm truy cập sẽ tại react/components/App.js

// app/assets/javascripts/react/components/App.js

var React = require('react');
var Router = require('react-router');
var RouteHandler = Router.RouteHandler;
var $ = require('jquery');

var App =
  React.createClass({
    componentWillMount: function() {
      $.ajax({
        method: 'GET',
        url: '/auth/is_signed_in.json'
      })
      .done(function(data){
        this.setState({signedIn: data.signed_in});
      }.bind(this));
    },
    getInitialState: function() {
      return {signedIn: null};
    },
    render:function(){
      return <RouteHandler signedIn={this.state.signedIn}/>;
    }
});
module.exports = App;

Bước 2: Hãy chắc chắn rằng chúng ta có thể truy cập và gửi đúng CSRF mã thông báo với mỗi request

Đây là một chút khó khăn, nhưng với thiết lập sau, tôi đã có thể lấy CSRF token từ phần đầu của trang bằng cách sử dụng sau đây.

// app/assets/javascripts/react/utils/Functions.js

var Functions = {
  getMetaContent: function(name) {
    var metas = document.getElementsByTagName('meta');

    for (var i=0; i<metas.length; i++) {
      if (metas[i].getAttribute('name') == name) {
        return metas[i].getAttribute('content');
      }
    }

    return '';
  }
}

module.exports = Functions;

Trên đây là tất cả chúng ta cần bây giờ. Chúng ta sẽ quay trở lại mà sau này. Nó sẽ là cần thiết cho tất cả các PUT, POST và DELETE yêu cầu chúng ta làm với ajax.

Bước 3: Có thể đăng nhập từ React app

// app/assets/javascripts/react/components/auth/SignInForm.js

var React = require('react');
var Functions = require('../../utils/Functions.js');
var _ = require('lodash');
var $ = require('jquery');

var SignInForm =
  React.createClass({
    _handleInputChange: function(ev) {
      // Get a deep clone of the component's state before the input change.
      var nextState = _.cloneDeep(this.state);

      //Update the state of the component
      nextState[ev.target.name] = ev.target.value;

      // Update the component's state with the new state
      this.setState(nextState);
    },
    getInitialState: function() {
      return {
        email: '',
        password: ''
      };
    },
    _handleSignInClick: function(e) {
      $.ajax({
        method: "POST",
        url: "/users/sign_in.json",
        data: {
          user: {
            email: this.state.email,
            password: this.state.password
          },
          authenticity_token: Functions.getMetaContent("csrf-token")
        }
      })
      .done(function(data){
        location.reload();
      }.bind(this));
    },
    render:function(){
      return (
          <form>
              <input type='email'
                name='email'
                placeholder='email'
                value={this.state.email}
                onChange={this._handleInputChange} />
              <input type='password'
                name='password'
                placeholder='password'
                value={this.state.password}
                onChange={this._handleInputChange} />
              <input type='submit' onClick={this._handleSignInClick} defaultValue='login' />
          </form>
      )
    }
  });
module.exports = SignInForm;

Bước 4: Có thể đăng xuất khỏi React app

// app/assets/javascripts/react/components/auth/SignOutLink.js

var React     = require('react');
var $         = require('jquery');
var Functions = require('../../utils/Functions.js');

var SignOutLink =
  React.createClass({
    render:function(){
      return (
        <a href="#" onClick={this._signOut}>Sign out</a>
      )
    },
    _signOut: function(){
      $.ajax({
        method: "DELETE",
        url: "/users/sign_out.json",
        data: {
          authenticity_token: Functions.getMetaContent("csrf-token")
        }
      }).done(function(){
        location.reload();
      });
    }
  });
module.exports = SignOutLink;

Bước 5: Có thể đăng kí tài khoảng bằng React app

// app/assets/javascriptsreact/components/auth/SignUpForm.js
var React          = require('react');
var _              = require('lodash');
var Functions      = require('../../utils/Functions.js');

var SignUpForm =
  React.createClass({
    _handleInputChange: function(ev) {
      // Get a deep clone of the component's state before the input change.
      var nextState = _.cloneDeep(this.state);

      //Update the state of the component
      nextState[ev.target.name] = ev.target.value;

      // Update the component's state with the new state
      this.setState(nextState);
    },
    getInitialState: function() {
      return {
        email: '',
        password: '',
        password_confirmation: '',
        name: ''
      };
    },
    _handleRegistrationClick: function(e) {
      $.ajax({
        method: "POST",
        url: "/users.json",
        data: {
          user: {
            email: this.state.email,
            uid: this.state.email,
            password: this.state.password,
            password_confirmation: this.state.password_confirmation,
            name: this.state.name,
            provider: "email"
          },
          authenticity_token: Functions.getMetaContent("csrf-token")
        }
      })
      .done(function(data){
        location.reload();
      }.bind(this));
    },
    render:function(){
      return (
          <form>
              <input type='text'
                name='name'
                placeholder='name'
                value={this.state.name}
                onChange={this._handleInputChange} />

              <input type='email'
                name='email'
                placeholder='email'
                value={this.state.email}
                onChange={this._handleInputChange} />

              <input type='password'
                name='password'
                placeholder='password'
                value={this.state.password}
                onChange={this._handleInputChange} />

              <input type='password'
                name='password_confirmation'
                placeholder='re-type password'
                value={this.state.password_confirmation}
                onChange={this.handleInputChange} />
            </div>
            <input onClick={this._handleRegistrationClick} defaultValue="sign up"/>
          </form>
      )
    }
  });
module.exports = SignUpForm;

Trên đây là các bước cần thiết để có thể xác thực React app bằng gem devise. Trên đây là một hướng dẫn cơ bản để kết hợp các stuff với nhau. Cảm ơn các bạn đã theo dõi.