クライアントAngularJS サーバーサイドRails5 におけるOmniauth 認証を試してみる

スクリーンショット 2016-08-16 0.43.10
AngularJS

去年にEOPESを公開してから1年半。
初めての外部公開サービスだったが、ソースは結構ごり押し部分も多かった。
特にこの値を変えたら連動してここの値も変わってみたいなことをたくさんやったので、ajax部分が大変なことになっている。

そんなこんなでもっといい方法ないかなーと探していると最近は画面部分はangularやbackboneで作成するみたいな流れになっている。
この流れに乗って自分もbackbone、angularを試すようになり始めた。
だんだん慣れてきたかなー と思い始めたので最近は前に作ったEOPESをangular + rails5 で再構築してみようと思い、作り直し始めた。
途中まで順調だったが、最後に認証部分で大きく躓いてなかなか前に進まない。

これではダメだと思い、angular + rails5 におけるomniauth認証を実験し、その軌跡を記事にすることとする。

今回はRails側はAPIを提供するのみで、画面系は全てAngular側で行うこととする。
また、認証にはdevise_token_authとng-token-authを用いる。

まずはクライアント側から作成する。

自分はangularの雛形作成として、以前 KarolAltamirano/generator-angular-webpack を利用したので今回もこれを利用して雛形を作成していく。
(このGenerator つい半年前くらいに使ったばかりなのにもう別の使ってくれ と言っているあたりフロントエンド側の移り変わりが早いことを示してますね。)

Readmeに従い、次のコマンドを打ち込んでいく。

npm install -g yo
npm install -g generator-angular-webpack

その後 プロジェクトディレクトリを作成し、移動し次のコマンドを打ち込む。

yo angular-webpack

なお、フォルダに移動せずに上記コマンドを打ったらディレクトリ作成してくれず、その場に展開され悲しい思いをした。

その後、gulp と打ち込み起動確認を行う。
無事 Webブラウザに風景が表示されればOK

スクリーンショット 2016-08-15 21.54.14

さて、次にクライアント側にng-token-authを導入する。

次のコマンドで導入した
なお、ng-token-authを利用するためには前提として angular-cookie(後当然angular) が必要なため、合わせて導入する。

npm install -save ng-token-auth
npm install -save angular-cookie

src/scripts/app/mApp.js を編集する

'use strict';

var angular = require('angular'),
    ngTouch = require('angular-touch'),
    ngSanitize = require('angular-sanitize'),
    ngResource = require('angular-resource')
    uiRouter = require('angular-ui-router'),
    mAnimations = require('./animations/_loader'),
    mCtrls = require('./controllers/_loader'),
    mDirectives = require('./directives/_loader'),
    mServices = require('./services/_loader'),
    angularCookie = require('angular-cookie'),
    ngTokenAuth = require('ng-token-auth');


/**
 * Register main angular app
 */
angular.module('mApp', [ngTouch, ngSanitize,ngResource, uiRouter, mAnimations, mCtrls, mDirectives, mServices,angularCookie, ngTokenAuth])
    .config(function ($stateProvider, $locationProvider, $urlRouterProvider, $authProvider) {
        'ngInject';

        $stateProvider
            .state('home', {
                url: '/',
                templateUrl: 'tpls/views/home.html',
                controller: 'MyCtrl'
            })
            .state('page1', {
                url: '/page1',
                templateUrl: 'tpls/views/page1.html',
                controller: 'MyCtrl'
            })
            .state('page1.detail', {
                url: '/detail',
                templateUrl: 'tpls/views/detail.html',
                controller: 'DetailCtrl'
            });

         $authProvider.configure({
             apiUrl: 'http://localhost:3010',
             emailSignInPath: '/auth/sign_in',
             storage: 'cookies',
             authProviderPaths: {
                 github:   '/auth/github'
             },
             tokenFormat: {
                 "access-token": "{{ token }}",
                 "token-type":   "Bearer",
                 "client":       "{{ clientId }}",
                 "expiry":       "{{ expiry }}",
                 "uid":          "{{ uid }}"
             }
         });
 

        $urlRouterProvider.otherwise('/');

        $locationProvider.html5Mode(true);
    });

12行目、13行目 で追加したangular-cookieとng-token-authを読み込んでいます。(後でngResourceを使うので 6行目等に記述を追加しておく)

合わせて、19行目にangularCookieとngTokenAuthの記述 を追加、20行目に$authProvideを追加。

40行目からは https://github.com/lynndylanhurley/ng-token-auth#configuration を参考に設定。

サーバーの接続先はlocalhostの3010ポートにするので apiUrlは http://localhost:3010 (この部分は実際使うときは可変にしておくべき)

まずはサインインだけ試すので emailSignInPathを記述。またGithubのアカウントでログインできるか試すので、authProviderPathsに githubの記述を追加。

次にログインするボタンとログイン後の稼動確認用のボタンを追加する
src/tpls/views/home.html に次の記述を追加した。

<button ng-click="githubLoginBtnClick()">
  Sign in with Github
</button>
<button ng-click="memberOnlyBtnClick()">
  Get Member Only Information
</button>

src/scripts/app/controllers/MyCtrl.js を編集。

'use strict';

var mCtrls = require('./_mCtrls'),
debug = require('debug'),
log = debug('Ctrls'),
loader = require('../../utilities/loader');

mCtrls.controller('MyCtrl',['$scope','$state','$stateParams','$auth','MemberService',
function ($scope,$state,$stateParams, $auth, MemberService) {
    log('test');
    $scope.test = 'test';
    console.log(loader.getLoader('main').getResult('app-data'));

    $scope.githubLoginBtnClick = function(){
        console.log('Github Login Btn Click');
        $auth.authenticate('github')
        .then(function(resp){
            console.log('Github Login Successful');
        })
        .catch(function(resp){

        });
    };

    $scope.memberOnlyBtnClick = function(){
        console.log('Member Only Btn Click');

        MemberService.query({

        }, function(response) {
            $scope.members = response.members;
        });

    }
}
]);

14行目からは「Sign in with Github」ボタンを押した時の処理を記述している。
25行目からは稼動確認用のボタンの記述を記載。 単純にサーバーに問い合わせて一覧を表示するということをしているだけ。

なお、memberServiceというServiceを追加しておく。

src/scripts/app/services/member.service.js

'use strict';

var mServices = require('./_mServices');

mServices.factory('MemberService',  function($resource) {

    return $resource("http://localhost:3010/members.json", {}, {
        query: {
            method: 'GET',
            params: {},
            isArray: true
        }
    });
});

これでクライアント側の記述はひとまず完成。

次にサーバーサイド(rails)側の記述を行っていく。

bundle exec rails new angular-omniauth-server-test --api

上記コマンドでAPIモードでプロジェクトを作成した。

gemfileに次を追加する

gem 'devise'
gem 'omniauth'
gem 'devise_token_auth'
gem 'omniauth-github'

https://github.com/lynndylanhurley/devise_token_auth#configuration-tldr を参考にUserモデルを作成する

bundle exec rails g devise_token_auth:install User auth

上記コマンドを実行すればapplication_controller と User に次の記述が追加されるはず。

app/controllers/application_controller.rb

class ApplicationController < ActionController::API
  include DeviseTokenAuth::Concerns::SetUserByToken
end

app/models/user.rb

class User < ActiveRecord::Base
  # Include default devise modules.
  devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :trackable, :validatable,
          :confirmable, :omniauthable
  include DeviseTokenAuth::Concerns::User
end

次にomniauth のプロバイダー設定を行っていく。
これも https://github.com/lynndylanhurley/devise_token_auth#omniauth-provider-settingsに従っていけば特に問題なし。

上記参考に次のようにした。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :github,        ENV['GITHUB_KEY'],   ENV['GITHUB_SECRET'],   scope: 'email,profile'
end

設定値部分についてはdotenv を利用し .envに記載

さて、ここまでで一度ログイン確認を行うが、いくつか問題が発生したので下記に記載する。

遭遇した問題1: OmniAuth::NoSessionError (You must provide a session to use OmniAuth.):

原因
セッションが無効になっているから. Rails5 の APIモードだと session middleware が含まれていないからエラーになるらしい。

対応方法その1
RailsのAPIモードをやめる

対応方法その2
次の記述をapplication.rbに記述

config.middleware.use Rack::MethodOverride
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
config.middleware.use ActionDispatch::Flash

参考: OmniAuth::NoSessionError with devise 2.1.2

遭遇した問題2: ログインのcallback後の ArgumentError (Before process_action callback :set_user_by_token has not been defined):

原因
Rails5 API Modeにおける devise_token_authの不具合

対応方法
最新版のdevise_token_authでは修正されているらしいのでGemfileの記述を次のように変えた。(そのうち標準でも適用されているはず)

gem 'devise_token_auth', :git => 'git://github.com/lynndylanhurley/devise_token_auth.git'

遭遇した問題3: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘null’ is therefore not allowed access.

原因
よく見るやつ。CORS (Cross-Origin Resource Sharing)。いくらでも情報が出てくるので詳細割愛。サーバー側とクライアント側でURLが違うことにより発生。

対応方法
丁寧にReadmeに書いてあるのでその通りやる。 https://github.com/lynndylanhurley/devise_token_auth#cors

これで 準備が整ったので稼動確認を行う。
ログインボタンを押して、次の画面を経由後、元の画面に戻ればOK

スクリーンショット 2016-08-16 0.43.10

この後 githubの認証だけでなくEve Onlineの認証や サインアウトボタンをつけたりした。

詳細については、ソースを参照。

クライアントサイド(Angular)
quick-xp/angular-omniauth-client-test

サーバーサイド(Rails5)
quick-xp/angular-omniauth-server-test

制限をかけたいAPIにはcontrollerに次の記述を追加する。

  before_action :authenticate_user!

これで認証が行われていない場合はエラーになる。

試しにやってみる。

まずはログインせずに「Get Member Only Information」ボタンを押した時。

スクリーンショット 2016-08-16 1.00.17

スクリーンショット 2016-08-16 1.01.45

認証エラーとなっていることがわかる。

次に、ログイン後に押した場合。

スクリーンショット 2016-08-16 1.00.55

スクリーンショット 2016-08-16 1.01.14

エラーがないことが確認出来る。今はDBに情報が入っていないので空が帰ってきてるが値が入っていればその値が返却されるはず。

なんとかできました。
次回はログインしてない時のボタンやリンク制御とかを試してみようと思います。

コメントはまだありません

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

スクリーンショット 2016-01-09 20.02.08
Ruby on Rails
Capistrano3を利用してBitbucketプライベートリポジトリにあるRailsアプリをデプロイしてみた

Railsアプリを配置する際、毎回手作業で頑張って配置してきたが、そろそろ自動デプロイを・・・ とい …

MarketTreeViewのイメージ
Ruby on Rails
[Eve Online 3rd party app] Market Tree Viewの実装 [EOPES制作記]

Eve Online 3rd party app のEOPES を作り始めてから約3ヶ月が経ちました …

-2015-03-22-19.51.35-e1427022407466
Ruby on Rails
EveOnline Authenticated Crestを使ったEOPES をリリースしました!!

前回の投稿から時間が立ってしまった。 というのもここ数ヶ月 Crestを作ったEve Online …