Imolog

技術的備忘録

Rails + Grape + API Keyの認証

RailsでのAPI開発にGrapeを使っていて、シンプルな認証処理を実装してみました。 https://mikecoutermarsh.com/rails-grape-api-key-authentication/ 自分で解釈する為に一部コードを変更したものをまとめてます。 ## How do works - ユーザー登録時は、Username/PasswordをAPIにPOSTする - ユーザー認証し、API Keyを返却する - その後の全てのAPIリクエストは、API Keyと一緒にリクエストする。 ## How do it ### API Keyモデルを作る モデルを作成してカラムは下記のようにします。 - access_token - expires_at - user_id ``` rails g model api_key access_token:string expires_at:datetime user_id:integer active:boolean ``` ### インデックスを追加 migrationファイルが作成されていると思うので、 __api_key__ と __user_id__ にインデックスを追加します。 ``` class CreateApiKeys < ActiveRecord::Migration def change create_table :api_keys do |t| t.string :access_token, null: false t.integer :user_id, null: false t.datetime :expires_at t.timestamps end add_index :api_keys, ["user_id"], name: "index_api_keys_on_user_id", unique: false add_index :api_keys, ["access_token"], name: "index_api_keys_on_access_token", unique: true end end ``` migrationを走らせて実際に適用されているかを確認して下さい。 ``` rake db:migrate ``` ### Tokenの作成 api_key.rbを開いて、下記のように書き換えます。 ``` class ApiKey < ActiveRecord::Base attr_accessible :access_token, :expires_at, :user_id before_create :generate_access_token before_create :set_expiration belongs_to :user def expired? DateTime.now >= self.expires_at end private def generate_access_token begin self.access_token = SecureRandom.hex end while self.class.exists?(access_token: access_token) end def set_expiration self.expires_at = DateTime.now+30 end end ``` これでAPI Keyの作成時にaccess_tokenが作成され、expires_atが設定されます。 ### Authentication helpersをGrapeに追加 APIが呼ばれた際にユーザー認証をする処理をGrapeのhelperに書き足します。 ``` helpers do def authenticate! error!('Unauthorized. Invalid or expired token.', 401) unless current_user end def current_user # トークンを検索 token = ApiKey.where(access_token: params[:token]).first if token && !token.expired? @current_user = User.find(token.user_id) else false end end end ``` ### GrapeにAPI Key作成処理を追加 __POST /api/auth__ で認証します。 __GET /api/ping__ でAPI Keyが正しいかどうかを返します。 ``` # /api/auth resource :auth do desc "Creates and returns access_token if valid login" params do requires :email, type: String, desc: "Email address" requires :password, type: String, desc: "Password" end post :login do user = User.where(email: params[:email]).first if user && user.authenticate(params[:password]) key = ApiKey.create(user_id: user.id) {token: key.access_token} else error!('Unauthorized.', 401) end end desc "Returns pong if logged in correctly" params do requires :token, type: String, desc: "Access token." end get :ping do authenticate! { message: "pong" } end end ```