Rails
devise
OAuth
AngularJS

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

More than 1 year has passed since last update.


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


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



  • 今回は、2-2-2の方法について、セキュアなToken認証を実現する方法を紹介します


    • AngularJSとRailsの組み合わせで使うなら、こちらの方が簡単かもしれません




devise_token_authとng-token-authを用いて認証を行う方法


  • 大まかなポイントを解説するのみにしましたので、詳しくはサンプルアプリケーションでご確認ください

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


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


APIをGrapeで書きたいけど、、、


  • Tokenの認証とか再生成は、DeviseTokenAuth::Concerns::SetUserByTokenでやっているのですが、これはActionControllerに依存しています

  • Rails側はAPIだけを提供するので、grape使いたいなと思ったら、上記の理由で使えませんでした。。。

  • 強引にやるなら、ココらへんの処理を自前で書かないといけないなぁと思ったら、既に書いてくれている方がいました。試してないですが、ご確認あれ。

Usage with Grape · Issue #73 · lynndylanhurley/devise_token_auth · GitHub


その他


AngularJSとRailsを1プロジェクトで管理する


  • そもそもAngularJSとRailsを分離したけど、実際は1プロジェクトとして管理したいという時にどうするかで悩みました


    • この辺りは別途書いてみたいと思いますが、angular-rails-templatesというgemを使って、AngularJSのテンプレートをasset pipelineに含めてやることで解決しました



pitr/angular-rails-templates

AngularJS, Rails 4.1 and UI Router Tutorial


Ref

lynndylanhurley/devise_token_auth

lynndylanhurley/ng-token-auth

How to Set Up Authentication with AngularJS and Ruby on Rails

Ng Token Auth Test