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を用いて認証を行う方法
- 大まかなポイントを解説するのみにしましたので、詳しくはサンプルアプリケーションでご確認ください
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にリクエストしてくれます
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をアップデートする)が可能になります
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認証ができます
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のフォーマットなどの設定ができますが、基本的にはデフォルトのまま使えました
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
などでフォームを送信してやります
-
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周りの処理は全く記述する必要がありません
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に含めてやることで解決しました
- この辺りは別途書いてみたいと思いますが、
AngularJS, Rails 4.1 and UI Router Tutorial
Ref
lynndylanhurley/devise_token_auth
How to Set Up Authentication with AngularJS and Ruby on Rails