Help us understand the problem. What is going on with this article?

Rails + Grape + API Keyの認証

More than 3 years have passed since last update.

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_keyuser_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
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away