ボクココ

個人開発に関するテックブログ

ユーザ登録・API 認証の仕組みを Rails で実現する

スマホアプリから会員の新規登録、ログインが両方できるようにAPIを作成中。ようやく自前でアクセストークンを作ってOAuth認証が出来たのでまとめておく。

まず何がしたいか?

  • スマホアプリでAPI認証ができるように、OAuthを自前で作成したい。

-> スマホアプリ側ではユーザ名とパスワードを入力すればトークンが取って来れて、そのトークンで各APIにアクセスすればユーザ固有の情報が取って来れるようになる仕組みを作る。

  • スマホアプリ側でユーザ作成も出来るようにしたい。

-> APIでユーザが作れるようにする。もちろんhttps前提。

環境

gemfile

ruby '2.0.0'
gem 'rails', '4.0.0'

gem 'rails-api'
gem 'active_model_serializers'

gem 'mongoid', '4.0.0.alpha1'
gem "moped", '2.0.0.beta6'

gem 'sorcery'
gem 'rack-cors', :require => 'rack/cors'

gem 'doorkeeper'

認証ではDeviseではなく、Sorcery を。理由は Rails の認証で Devise ではなく Sorcery という選択 - ボクココ

ORM は ActiveRecord ではなく Mongoid を利用。

Sorcery と Doorkeeper のセットアップ

ここら辺はもうマニュアル通りで。前章のGemfile構成なら特に問題なくいけるはず。

rails g mongoid:config
rails g sorcery:install
rails g doorkeeper:install

config/initializers/doorkeeper.rb

doorkeeper で使用するORM と認証方法を指定。今回はdoorkeeperのpassword token で認証するので、以下のような感じでセット。

  orm :mongoid4
  resource_owner_from_credentials do |routes|
    User.authenticate(params[:username], params[:password])
  end

config/application.rb

Moped::BSONが undefined だよっていちいち言ってくるので、以下のように修正

Bundler.require(*Rails.groups)

# @see https://github.com/mongoid/mongoid/issues/3455
Moped::BSON = BSON

API でユーザ作成

通常はHTMLでユーザを作るけど、SorceryならAPIで簡単にユーザ作成できる! app/controllers/users_controller.rb

class UsersController < ApplicationController
  doorkeeper_for :show

  def create
    @user = User.new(user_params)
    if @user.save
      head :created
    else
      head :bad_request
    end
  end

  def show
    render json: current_user
  end

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

これで localhost:9000/users にpost で users[email], users[password], users[password_confirmation] を送れば作れる。

Doorkeeper の関門

ここが最大の難所。 まずちょっと解説する。 Doorkeeper で OAuth 認証をする訳だが、Doorkeeper には Application って概念があって、これは例えば MyAppWeb, MyAppAndroid, MyAppiPhone みたいに分けたり、サードパーティにAPIを提供したりで Application を作る分ける必要がある。それの id と secret とユーザのid, password をセットで送ることでアクセストークンが取得できる。

ここで問題は、DoorkeeperはこのApplicationの作成をHTMLベースで作ること前提でしか作られていない、という点だ。 doorkeeper のソースを見ると、 app/controllers/doorkeeper/applications_controller.rb あたりに書いてある。同様にViewも提供されているので、普通なら問題なく作れる。

ただ、今回はrails-api でrailsアプリを作っているため、レンダリングの仕組みはrequireしていない。もちろんそこで妥協するのも手ではあるが、それではかっこわるすぎる。てことでなんとかAPIでapplicationを作れるようにしなきゃならない。

Doorkeeper 内の applications_controller.rb を再オープン

問題となっているこいつを再定義してやる。具体的には app/controllers/doorkeeper/applications_controller.rb を作成。

module Doorkeeper
  class ApplicationsController < Doorkeeper::ApplicationController
    respond_to :json

    before_filter :authenticate_admin!
    before_filter :set_application, :only => [:show, :edit, :update, :destroy]

    def index
      @applications = Application.all
      render json: @applications
    end

    def create
      @application = Application.new(application_params)
      if @application.save
        render json: {status: "created"}
      else
        render json: {status: "failed"}
      end
    end

 ~~~~~~
  end
end

これで、http://localhost:3000/oauth/applications あてにpost でapplication[name]、application[redirect_uri] を含めて送れば登録できる!

アクセストークン取得

これさえクリアすればゴールは近い。

http://localhost:3000/oauth/token.json へ post で grant_type=password, client_id, client_secret, username, password を入れてやればAccessTokenが取得できる。

取得したアクセストークンで、 http://localhost:3000/users/ にGET で access_tokenパラメータを付けてやれば、 doorkeeper_for を通り抜け、current_user が取って来れるようになる。

あ、ちなみに app/controllers/application_controller.rb はこうする。

class ApplicationController < ActionController::API
  def current_user
    @current_user ||= User.find_by_id(doorkeeper_token.resource_owner_id) if doorkeeper_token
  end
end

この先

まずテストコード書くか。そしてJenkins環境を作る。そんでもってJenkinsからHerokuへ自動デプロイするようにして、 vagrantで作ったvmに cap deploy できるようにして本番デプロイ(AWS or さくらVPS)をできるようにする。

そしたらアプリ固有の実装に突入!!