AngularJSとRailsをTokenベースの認証で繋ぐ方法(devise_token_auth + ng-token-auth)


## AngularJSとRailsの関係性
- AngularJSとRailsをどのように分離するのかにはいくつかのオプションがあります
- 大きくは、AngularJSがRailsの上に乗るか、Railsとは完全に分離するかの2つかなと思います
	- 1.ベースはRailsで遷移やViewの記述を行い、AngularJSは一部分のみ適用する
	- 2.RailsはAPIを提供するのみで、AngularJS側で遷移やViewのテンプレートを記述する
- 今回は、2.を対象に考えます

## AngularJSとRailsを分離して、セキュアに繋ぐ方法
- サーバーサイドで認証が必要な場合、大きく2つの実装方法があります
	- 2-1. Cookieベースの認証
	- 2-2. Tokenベースの認証
- 最近ではTokenベースの実装が多いようです。この2つの比較については、以下を参考にされると良いかと思います

[Cookies vs Tokens. Getting auth right with Angular.JS](https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/)

### Token認証の実装方法
- Token認証にもいくつかの実装方法がありますが、ここでは2つ挙げてみます
	- 2-2-1. doorkeeperとgrapeを用いて、OAuth2認証を行う方法
	- 2-2-2. devise_token_authとng-token-authを用いてOAuth2認証/Email認証を行う方法

- 2-2-1については、以前記事を書いたので、こちらもご参考にしてください

[AngularJS - AngularとRailsを分離し、セキュアに繋ぐ方法 - Qiita](http://qiita.com/jwako/items/4b134a0d7c539971bded)

- **今回は、2-2-2の方法について、セキュアなToken認証を実現する方法を紹介します**
	- AngularJSとRailsの組み合わせで使うなら、こちらの方が簡単かもしれません

## devise_token_authとng-token-authを用いて認証を行う方法
- 大まかなポイントを解説するのみにしましたので、詳しくはサンプルアプリケーションでご確認ください

[jwako/devise_token_auth_demo](https://github.com/jwako/devise_token_auth_demo)

### devise_token_auth
- devise_token_authは、DeviseをリソースオーナーとしてRailsでToken認証をするためのgemです
	- OmniAuthを使ってOAuth2認証を行うこともできますし、Email/PassでEmail認証を行うこともできます
	- ng-token-authと併用することで、シームレスにAngularJSとRailsを繋ぐことができます

#### Routes
- routesに```mount_devise_token_auth_for 'User', at: 'auth'```を記述するだけで、サインアップ、サインイン、サインアウトなどの認証に必要なルーティングが利用可能になります
- さらに、ng-token-authとシームレスにつながってるので、Pathを記述せずともクライアント側からこれらのURLにリクエストしてくれます

```routes.rb
  namespace :api, defaults: { format: :json } do
    scope :v1 do
      resources :users, only: :index
    end
  end
  mount_devise_token_auth_for 'User', at: '/api/v1/auth'
```

- ただし、現状(v0.1.31)では、```mount_devise_token_auth_for```をnamespaceに含めるとresource_nameが変わってしまうため、namespaceの外に出して、```at: '/api/v1/auth'```としてやる必要がありました

[Routes not properly set · Issue #101 · lynndylanhurley/devise_token_auth](https://github.com/lynndylanhurley/devise_token_auth/issues/101)

#### Controllers
- ```DeviseTokenAuth::Concerns::SetUserByToken```をincludeすることで、Token認証やAuthヘッダーの更新(devise_token_authはリクエスト毎にTokenをアップデートする)が可能になります

```app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :null_session
...
```

- apiは、controllersディレクトリ以下に置きます
- Token認証したいControllerのbefore_actionに```authenticate_user!```を追加してやればToken認証ができます

```app/controllers/api/users_controller.rb
class Api::UsersController < ApplicationController
  before_action :authenticate_user!

  def index
    @users = User.all
  end

end
```

### ng-token-auth
- ng-token-authは、AngularJSでToken認証を実現するためのライブラリです
	- 単体でも使えますが、devise_token_authを使えば、サーバーサイドとシームレスに繋げます

#### config
- ```.config ($authProvider) ->```のところでPathやTokenのフォーマットなどの設定ができますが、基本的にはデフォルトのまま使えました

```app/assets/javascripts/angular_app.coffee
deviseTokenAuthApp = angular.module('deviseTokenAuthApp', [
  'ng-token-auth'
  'ui.router'
  'templates'
  'ngResource'
])
.config ($httpProvider) ->
  $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
.config ($authProvider) ->
  $authProvider.configure
    apiUrl: '/api/v1'
.config ($stateProvider, $urlRouterProvider, $locationProvider) ->
  $stateProvider
    .state 'top',
      url: '/'
      templateUrl: 'top/show.html'
      controller: 'AuthCtrl'
    .state 'home',
      url: '/home'
      templateUrl: 'home/show.html'
      controller: 'HomeCtrl'

  # default fall back route
  $urlRouterProvider.otherwise('/')
```

#### controllers
- Angularのログイン画面のControllerは以下のように書いてみました
	- ```$auth.submitRegistration```や```$auth.submitLogin```などでフォームを送信してやります

```app/assets/javascripts/controllers/auth_controllers.coffee
angular.module('deviseTokenAuthApp').controller("AuthCtrl", ['$rootScope', '$scope', '$auth', '$state', ($rootScope, $scope, $auth, $state) ->

  $scope.handleRegBtnClick = () -> 
    $auth.submitRegistration($scope.registrationForm)
      .then (resp) ->
        $scope.registrationForm = {}
        $state.go 'home'
      .catch (resp) ->
        $scope.registrationForm.password = ""
        $scope.registrationForm.password_confirmation = ""

  $scope.handleLoginBtnClick = () -> 
    $auth.submitLogin($scope.loginForm)
      .then (resp) ->
        $state.go 'home'

])
```

- アクセス制限されたリソースへのアクセスはこんな感じで書けます
	- ng-auth-tokenは、認証後の全てのリクエストをインターセプトしてAuthヘッダーを付加するので、Token周りの処理は全く記述する必要がありません

```app/assets/javascripts/controllers/home_controllers.coffee
angular.module('deviseTokenAuthApp').controller("HomeCtrl", ['$scope', 'User', ($scope, User) ->

  @userService = new User(serverErrorHandler)
  @userService.all().$promise.then (result) ->
    $scope.users = result.users

  serverErrorHandler = ->
    console.log("There was a server error.")

])
```

### 実装時のハマりポイント
- 簡単には以上ですが、実装してみた際にハマった箇所をまとめてみました	

#### ngResourceを使う場合に、Authヘッダーが付加されない
- Base URLと異なるリクエストをした場合は、Authヘッダーが付加されないため(Authentication headers will only be added to requests with this value as the base URL)、リクエストが```/api/v1```で統一されるようにroutesを調整してやる必要がありました

[Using ng-token-auth with ngResource · Issue #36 · lynndylanhurley/ng-token-auth · GitHub](https://github.com/lynndylanhurley/ng-token-auth/issues/36)

#### APIをGrapeで書きたいけど、、、
- Tokenの認証とか再生成は、```DeviseTokenAuth::Concerns::SetUserByToken```でやっているのですが、これはActionControllerに依存しています
- Rails側はAPIだけを提供するので、grape使いたいなと思ったら、上記の理由で使えませんでした。。。
- 強引にやるなら、ココらへんの処理を自前で書かないといけないなぁと思ったら、既に書いてくれている方がいました。試してないですが、ご確認あれ。

[Usage with Grape · Issue #73 · lynndylanhurley/devise_token_auth · GitHub](https://github.com/lynndylanhurley/devise_token_auth/issues/73)

## その他
### AngularJSとRailsを1プロジェクトで管理する
- そもそもAngularJSとRailsを分離したけど、実際は1プロジェクトとして管理したいという時にどうするかで悩みました
	- この辺りは別途書いてみたいと思いますが、```angular-rails-templates```というgemを使って、AngularJSのテンプレートをasset pipelineに含めてやることで解決しました

[pitr/angular-rails-templates](https://github.com/pitr/angular-rails-templates)

[AngularJS, Rails 4.1 and UI Router Tutorial](http://start.jcolemorrison.com/angularjs-rails-4-1-and-ui-router-tutorial/)

## Ref

[lynndylanhurley/devise_token_auth](https://github.com/lynndylanhurley/devise_token_auth)

[lynndylanhurley/ng-token-auth](https://github.com/lynndylanhurley/ng-token-auth)

[How to Set Up Authentication with AngularJS and Ruby on Rails](https://www.airpair.com/ruby-on-rails/posts/authentication-with-angularjs-and-ruby-on-rails)

[Ng Token Auth Test](http://ng-token-auth-demo.herokuapp.com/#/)