Rails api mode login
Rails 7 API モード:認証と管理機能の実装
Railsを昔勉強していたときは、erbテンプレートエンジンでログインをしておりました。しかし現代では、ViewはReact.js
かVue.js
で作成して、MVCといいながらコントローラーとモデルしか使いません😅
データベースはaqlite
を使用しています。
Githubのコミット履歴載せておきますこちら必要であれば参考にしてみてください。
1. プロジェクトのセットアップ
プロジェクトと名はなんでも良いです。
rails new your_app_name --api
cd your_app_name
2. 必要なgemの追加
Gemfile
に以下を追加:
gem 'bcrypt', '~> 3.1.7'
gem 'jwt'
そして、以下を実行:
bundle install
3. ユーザーモデルの作成
rails generate model User email:string password_digest:string
rails generate migration AddAdminToUsers admin:boolean
rails db:migrate
4. ユーザーモデルの設定
app/models/user.rb
:
class User < ApplicationRecord
has_secure_password
validates :email, presence: true, uniqueness: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, length: { minimum: 6 }, if: -> { new_record? || !password.nil? }
def admin?
admin
end
end
5. JWT用のサービスクラスの作成
app/services/json_web_token.rb
:
class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
def self.encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, SECRET_KEY)
end
def self.decode(token)
decoded = JWT.decode(token, SECRET_KEY)[0]
HashWithIndifferentAccess.new decoded
end
end
6. 認証コントローラーの作成
app/controllers/authentication_controller.rb
:
class AuthenticationController < ApplicationController
skip_before_action :authenticate_request
def login
@user = User.find_by_email(params[:email])
if @user&.authenticate(params[:password])
token = JsonWebToken.encode(user_id: @user.id)
time = Time.now + 24.hours.to_i
render json: { token: token, exp: time.strftime("%m-%d-%Y %H:%M"),
email: @user.email }, status: :ok
else
render json: { error: 'unauthorized' }, status: :unauthorized
end
end
end
7. アプリケーションコントローラーの更新
app/controllers/application_controller.rb
:
class ApplicationController < ActionController::API
before_action :authenticate_request
attr_reader :current_user
private
def authenticate_request
header = request.headers['Authorization']
header = header.split(' ').last if header
begin
@decoded = JsonWebToken.decode(header)
@current_user = User.find(@decoded[:user_id])
rescue ActiveRecord::RecordNotFound => e
render json: { errors: e.message }, status: :unauthorized
rescue JWT::DecodeError => e
render json: { errors: e.message }, status: :unauthorized
end
end
end
8. ユーザー登録コントローラーの作成
app/controllers/users_controller.rb
:
class UsersController < ApplicationController
skip_before_action :authenticate_request, only: [:create]
def create
@user = User.new(user_params)
if @user.save
token = JsonWebToken.encode(user_id: @user.id)
time = Time.now + 24.hours.to_i
render json: { token: token, exp: time.strftime("%m-%d-%Y %H:%M"),
email: @user.email }, status: :created
else
render json: { errors: @user.errors.full_messages },
status: :unprocessable_entity
end
end
private
def user_params
params.permit(:email, :password, :password_confirmation)
end
end
9. 管理者用コントローラーの作成
app/controllers/admin_controller.rb
:
class AdminController < ApplicationController
before_action :ensure_admin
def users
@users = User.all
render json: @users, status: :ok
end
def update_user
@user = User.find(params[:id])
if @user.update(user_params)
render json: @user, status: :ok
else
render json: { errors: @user.errors.full_messages }, status: :unprocessable_entity
end
end
def delete_user
@user = User.find(params[:id])
@user.destroy
head :no_content
end
private
def ensure_admin
render json: { error: 'Unauthorized' }, status: :unauthorized unless current_user.admin?
end
def user_params
params.permit(:email, :password, :password_confirmation, :admin)
end
end
10. ルーティングの設定
config/routes.rb
:
Rails.application.routes.draw do
post '/auth/login', to: 'authentication#login'
post '/signup', to: 'users#create'
namespace :admin do
get '/users', to: 'admin#users'
put '/users/:id', to: 'admin#update_user'
delete '/users/:id', to: 'admin#delete_user'
end
end
11. 使用方法
ユーザー登録
POST /signup
{
"email": "user@example.com",
"password": "password123",
"password_confirmation": "password123"
}
ログイン
POST /auth/login
{
"email": "user@example.com",
"password": "password123"
}
管理者用エンドポイント
- ユーザー一覧取得:
GET /admin/users
- ユーザー情報更新:
PUT /admin/users/:id
- ユーザー削除:
DELETE /admin/users/:id
※ これらのエンドポイントには管理者権限を持つユーザーのみがアクセス可能です。
12. 注意点
- この実装は基本的な機能のみを提供しています。実際のアプリケーションではさらなるセキュリティ対策が必要です。
- 管理者権限の付与は現在、直接データベースで行う必要があります。
- フロントエンド(例:React, Vue.js)で管理画面のUIを別途実装する必要があります。
- 本番環境では必ずHTTPS通信を使用し、適切なCORS設定を行ってください。
- より強固なセキュリティのために、以下の実装も検討してください:
- 強力なパスワードポリシー
- アカウントロックアウト機能
- 二要素認証
- APIレートリミット
この設定で、Rails 7 API モードでの基本的な認証システムと管理機能が実装されます。必要に応じて、これらの基本機能をカスタマイズや拡張してください。
curlでログインをしてみる
HTTP POSTしないとできるはずがないな。やってみよう。POSTMAN持ってる人いたらそちらがいいと思いますので使ってください。
curl -X POST http://localhost:3000/signup \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "password123", "password_confirmation": "password123"}'
error code
hashimotojunichi@hashimotojunichinoMacBook-Pro icchy_api % curl -X POST http://localhost:3000/signup
-H "Content-Type: application/json"
-d '{"email": "user@example.com", "password": "password123", "password_confirmation": "password123"}'
{"status":500,"error":"Internal Server Error","exception":"#\u003cNoMethodError: undefined methodsecrets' for #\u003cIcchyApi::Application\u003e\u003e","traces":{"Application Trace":[{"exception_object_id":18240,"id":0,"trace":"app/services/json_web_token.rb:2:in
\u003cclass:JsonWebToken\u003e'"},{"exception_object_id":18240,"id":1,"trace":"app/services/json_web_token.rb:1:in\u003cmain\u003e'"},{"exception_object_id":18240,"id":6,"trace":"app/controllers/users_controller.rb:7:in
create'"}],"Framework Trace":[{"exception_object_id":18240,"id":2,"trace":"\u003cinternal:/Users/hashimotojunichi/.rbenv/versions/3.2.5/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb\u003e:38:inrequire'"},{"exception_object_id":18240,"id":3,"trace":"\u003cinternal:/Users/hashimotojunichi/.rbenv/versions/3.2.5/lib/ruby/3.2.0/rubygems/core_ext/kernel_require.rb\u003e:38:in
require'"},{"exception_object_id":
json_web_token.rb
の修正が必要
class JsonWebToken
SECRET_KEY = Rails.application.credentials.secret_key_base || Rails.application.secret_key_base
def self.encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
JWT.encode(payload, SECRET_KEY)
end
def self.decode(token)
decoded = JWT.decode(token, SECRET_KEY)[0]
HashWithIndifferentAccess.new decoded
rescue JWT::DecodeError
nil
end
end
新規登録をやり直す
curl -X POST http://localhost:3000/signup \
-H "Content-Type: application/json" \
-d '{"email": "hoge@example.com", "password": "password123", "password_confirmation": "password123"}'
多分成功?
hashimotojunichi@hashimotojunichinoMacBook-Pro icchy_api % curl -X POST http://localhost:3000/signup \
-H "Content-Type: application/json" \
-d '{"email": "hoge@example.com", "password": "password123", "password_confirmation": "password123"}'
{"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJleHAiOjE3MjY0ODczMzh9.cZIw6yQCFd-yztVfQavxbqC6yk_zTex2Lt-8MXG_6bI","exp":"09-16-2024 20:48","email":"hoge@example.com"}% hashimotojunichi@hashimotojunichinoMacBook-Pro icchy_api %
curlでログインしてみる
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "hoge@example.com", "password": "password123"}'
成功したみたいだな。
hashimotojunichi@hashimotojunichinoMacBook-Pro icchy_api % curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "hoge@example.com", "password": "password123"}'
{"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJleHAiOjE3MjY0ODc0Njl9.rR285wCTwifW0KGiIAjoxaIBdjQOjhF_DPdzMGClKeg","exp":"09-16-2024 20:51","email":"hoge@example.com"}% hashimotojunichi@hashimotojunichinoMacBook-Pro icchy_api %
感想
まだサンプル作って練習しているところなのでゆっくり時間をかけて勉強したいなと思います。サーバーを構築してデータベースも構築して認証機能があるREST APIを作るのが目標ですね。
Discussion