読者です 読者をやめる 読者になる 読者になる

EduTalk

教育に関するテクノロジーやスタートアップ、考えたことについて発信中。

Rails APIとEmber CLIでユーザー登録、ログインを実装する - パート3

f:id:atsuhio:20150411183835j:plain
このシリーズではRails APIとEmber CLIを利用してユーザー登録、ログインを実装していきます。

前回、パート2ではEmber CLIアプリケーション側でステート状態を基にテンプレートで「ログインしていません。」というメッセージを表示するところまで進めました。
今回は、実際にログインのアクションを実装していきます。

ログアウト中はSecretページへのアクセスを出来ないようにする。

その前に、現状だとログインしていても、していなくてもSecretページに入ることが出来てしまうので、これを修正していきます。これを行うにはRoute内で、Modelを読み込む前にユーザーのログイン状態をチェックし、ログインしていなければLoginRouteへと移動するようにします。

これを直接、secretルートで行ってもよいのですが今後、ログイン後の画面が増えた時に毎回それをチェックをするのは面倒です。そのため、AuthRouteというルートを作成し、SecretRouteではこれをextendするようにします。

auth-test-web/app/routes/auth.js
import Ember from 'ember';

export default Ember.Route.extend({
  beforeModel: function() {
    if (this.get('auth.loggedOut')) {
      this.transitionTo('login');
    }
  }
});



auth-test-web/app/routes/secret.js
import Ember from 'ember';
import AuthRoute from './auth';

export default AuthRoute.extend({
});

IndexページからSecretページヘアクセスしようとすると、自動的にLoginページでリダイレクトされるようになったはずです。

Loginアクションを実装する。

まずはLoginコントローラーにloginアクションを登録します。

/auth-test-web/app/controllers/login.js
import Ember from 'ember';

export default Ember.Controller.extend({
  actions: {
    login: function() {
      var data = this.getProperties('email', 'password');
      var auth = this.get('auth');
      auth.login(data);
    },
    loginSucceeded: function() {
      this.transitionToRoute('secret');
    },
    loginFailed: function() {
      this.set('password', null);
    }
  }
});

Loginアクション内で、Authサービスのlogin関数を呼び出し、ログインに成功すればSecretページへリダイレクトし、失敗すればパスワードフォームの内容が消されるようにします。実際にAuthサービス内のログイン関数を実装します。

import Ember from 'ember';
import config from 'auth-test-web/config/environment';
import { raw as icAjaxRaw } from 'ic-ajax';

export default Ember.Service.extend({
  init: function() {
  ....
  },
  login: function(data) {
    var _this = this;
    this.set('state', 'logging-in');
    return icAjaxRaw(this.get('serverTokenEndpoint') + '/users/authenticate', {
      type: 'post',
      data: {
        email: data.email,
        password: data.password
      }
    }).then(function(result) {
      _this.loginSucceeded(result);
    }, function(error) { 
      _this.loginFailed(error);
    });
  },
  loginSucceeded: function(result) {
    console.log(result.response)
  },
  loginFailed: function(error) {
   console.log(error);
  }
  ....

AjaxコールをAPI側のエンドポイントへ送り、200が返ってくればloginSucceededを呼び出し、401が返ってくればloginFailedを呼び出します。

Rails側のAuthenticateメソッドを実装する。

Rails側のauthenticateメソッドを実装していきます。

class Api::V1::UsersController < ApplicationController
  def authenticate
    user = User.find_by_email(params[:email])
    if user && user.authenticate(params[:password])
      render json: { userId: user.id, authToken: JWT.encode(user.attributes, 'secret') }
    else
      render json: { error: "Invalid email or password." }, status: 401
    end
  end
end

Rack CORS

authenticateメソッドが出来たので、実際にパート1で作ったユーザーのEmailとパスワードでログインを試してみます。すると、コンソールに下記のようなメッセージが表示されるはずです。

XMLHttpRequest cannot load http://api.auth-test-api.dev/v1/users/authenticate. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access.

Rack CORSを入れて、localhost:4200からのアクセスを許可します。

gem 'rack-cors', :require => 'rack/cors'
auth_test_api/config/application.rb
module AuthTestApi
  ....
  class Application < Rails::Application
    config.middleware.use Rack::Cors do
      allow do
        origins '*'
        resource '*', :headers => :any, :methods => [:get, :post, :delete, :put, :options]
      end
    end
  end
end

powder restartをして、再度ログインを試して見て下さい。今回は、コンソールで以下のように表示されているはずです。

Object {userId: 1, authToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1....."}

次に、AuthService内のloginSucceededとloginFailedを実装していきます。

/auth-test-web/app/services/auth.js
export default Ember.Service.extend({
...
  loginSucceeded: function(result) {
    var response = result.response
    this.setProperties({
      userId: response.userId,
      authToken: response.authToken,
      state: 'logged-in'
    });
    this.sendToApp('loginSucceeded');
  },
  loginFailed: function(error) {
    this.set('state', 'logged-out');
    this.sendToApp('loginFailed');
  },
  sendToApp: function(name) {
    var controller = this.container.lookup('controller:login');
    try {
      controller.send(name);
    }
    catch(error) {
      console.log(error);
    }
  },
...
});



ここで、サービスからコントローラー内のアクションを直接呼び出すことが出来ないため、sendToAppという関数内でcontainerに直接アクセスしてloginController内のアクションを呼び出しています。

実際にログインを試してみましょう。ログインに失敗するとパスワードが空になり、成功するとSecretページへ飛ぶはずです。

しかし、まだ問題があります。試しにページをリロードしてみて下さい。Loginページにリダイレクトされてしまいます。これは、localStorageにセッション情報が保存されていないためです。

そこで、ログインした時にlocalStorageにセッション情報が保存されるようにしていきます。
Authサービスに以下を追記します。

/auth-test-web/app/services/auth.js
  propertyChanged: function(propertyName) {
    var data = this.get(propertyName);
    var _this = this;
    if (Ember.isEmpty(data)) {
      localStorage.removeItem(propertyName);
    } else {
      console.log("handling property change of " + propertyName);
      localStorage.setItem(propertyName, this.get(propertyName));
    }
  },
  authTokenObserver: Ember.observer(function() {
    this.propertyChanged('authToken');
  }, 'authToken'),
  userIdObserver: Ember.observer(function() {
    this.propertyChanged('userId');
  }, 'userId'),

ここでは、Ember.Observerクラスを利用して、Authサービス内のuserId、authTokenプロパティが変わったときにpropertyChanged関数を呼び出すようにしています。
propertyChanged関数では、プロパティがnullだった場合にはlocalStorage内からセッション情報を消し、プロパティがある場合は、そのプロパティの値を保存します。

もう一度、ログインをしてからリロードしてみると、無事ログイン状態が維持しているはずです。

ログイン後のSecretページで名前が「◯◯さん」と表示されたままなのでユーザーの名前が表示されるように変更します。

まずは、Secretルート上のモデルを設定します。

/auth-test-web/app/routes/secret.js
import Ember from 'ember';
import AuthRoute from './auth';

export default AuthRoute.extend({
  model: function() {
    return this.store.find('user', this.get("auth.userId"));
  },
  setupController: function(controller, model) {
    controller.set('model', model)
  }
});

this.store.find('user', this.get("auth.userId"));はこの場合は、"http://api.auth-test-api.dev/v1/users/1"へアクセスしますが、まだRailsAPI側でshowメソッドを実装してないため、エラーが発生してしまいます。

そこで、RailsAPI側を実装していきます。

まずはUserSerializerを作成します。

cd auth_test/auth_test_api
rails g serializer user name email

次に、UsersController内のshowメソッドを実装します。

class Api::V1::UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    render json: @user, root: 'user'
  end
  .....
end

powder restart後、http://api.auth-test-api.dev/v1/users/1にアクセスしてみると、userのjsonデータが返ってきます。

{"user":{"id":1,"name":"Atsuhiro Teshima","email":"my@email.com"}}

無事に、SecretControllerにモデルが登録されたので、実際に名前を表示します。

auth-test-web/app/templates/secret.hbs
<p>秘密のページへようこそ。{{name}}さん</p>


auth-test-web/app/controllers/secret.js
import Ember from 'ember';

export default Ember.Controller.extend({
  name: function() {
    return this.get('model.name');
  }.property('model.name')
});

無事に、ユーザーの名前がシークレットページに表示されたはずです。


次回パート4では、ログアウトとユーザーの登録を実装していきます。

リンガルボックスでは現在CTOを募集しています。

リンガルボックスでは、オンライン英会話スクール「リンガルボックス」を運営しています。既にオンライン英会話では上場しているレアジョブに、DMM英会話、ラングリッチなど多数の会社がありますが、レアジョブの会員が3万人ほどで、日本だけでも語学学校に通う生徒数全体の中(500万人と言われています。)ではまだ小さな割合しか占めていないこと、まだ開拓の全く進んでいないブラジルやロシア、ヨーロッパなどの市場があること、オンライン英会話のサービス内容自体にもイノベーション余地があることなど、まだまだ可能性があると考えています。しかし、リンガルボックスの考えるビジョンを実現していくためには高い技術力が不可欠です。(こんなチュートリアルを書いていますが、代表は営業出身で技術力は全然高くないですw)
そうした訳で、現在リンガルボックスではCTO(Co-founder)を募集中です。

EdTech、Adaptive Learning、Single Page Application(リンガルボックスではEmber.jsを利用しています。)、Node.js、Socket.io、WebRTC、Ruby on RailsREST API、アプリ開発といったキーワードにピンときた方は是非hiro(at)lingualbox.comまでご連絡下さい。