Я использовал эту серию в качестве отправной точки для бэкенда Rails для веб-сайта портфолио работ.Адаптация была в основном простой, и она делает то, что я хочу.Одна большая проблема заключается в том, что «index» и «show» (действия чтения) должны быть доступны без аутентификации, в то время как «create», «update» и «delete» (действия записи) должны требовать допустимого JWT.
Следуя подходу, используемому для исключения маршрутов регистрации и входа в систему из аутентификации, я попытался
skip_before_action :authorize_request, only: [:index, :show]
в соответствующем контроллере.Это, однако, приведет к аварийному завершению работы приложения с
NoMethodError (undefined method `works' for nil:NilClass):
app/controllers/works_controller.rb:10:in `index'
Хотя проблема кажется очевидной - если пропустить действие аутентификации, класс не будет создан, - по крайней мере для меня это не исправление.Может ли кто-нибудь помочь, пожалуйста?
Код для проекта здесь .
Контроллер приложений
class ApplicationController < ActionController::API
include Response
include ExceptionHandler
# called before every action on controllers
before_action :authorize_request
attr_reader :current_user
private
# Check for valid request token and return user
def authorize_request
@current_user = (AuthorizeApiRequest.new(request.headers).call)[:user]
end
end
Контроллер «Works»
class WorksController < ApplicationController
#skip_before_action :authorize_request, only: [:index, :show]
before_action :set_work, only: [:show, :update, :destroy]
# GET /works
def index
@works = current_user.works
json_response(@works)
end
# POST /works
def create
@work = current_user.works.create!(work_params)
json_response(@work, :created)
end
# GET /works/:id
def show
json_response(@work)
end
# PUT /works/:id
def update
@work.update(work_params)
head :no_content
end
# DELETE /works/:id
def destroy
@work.destroy
head :no_content
end
private
def work_params
# whitelist params
params.permit(:title, :nature, :role, :client, :timeframe, :description, :images, :url, :blog_post)
end
def set_work
@work = Work.find(params[:id])
end
end
Контроллер «Users»
class UsersController < ApplicationController
skip_before_action :authorize_request, only: :create
def create
user = User.create!(user_params)
auth_token = AuthenticateUser.new(user.username, user.password).call
response = { message: Message.account_created, access_token: auth_token }
json_response(response, :created)
end
def show
json_response(username: current_user.username)
end
private
def user_params
params.permit(
:username,
:password,
:password_confirmation
)
end
end
Контроллер «Authentication»
class AuthenticationController < ApplicationController
skip_before_action :authorize_request, only: :authenticate
# return auth token once user is authenticated
def authenticate
auth_token =
AuthenticateUser.new(auth_params[:username], auth_params[:password]).call
json_response(access_token: auth_token)
end
private
def auth_params
params.permit(:username, :password)
end
end
Помощник AuthenticateUser
class AuthenticateUser
def initialize(username, password)
@username = username
@password = password
end
# Service entry point
def call
JsonWebToken.encode(user_id: user.id) if user
end
private
attr_reader :username, :password
# verify user credentials
def user
user = User.find_by(username: username)
return user if user && user.authenticate(password)
# raise Authentication error if credentials are invalid
raise(ExceptionHandler::AuthenticationError, Message.invalid_credentials)
end
end
Помощник AuthorizeApiRequest
class AuthorizeApiRequest
def initialize(headers = {})
@headers = headers
end
# Service entry point - return valid user object
def call
{
user: user
}
end
private
attr_reader :headers
def user
# check if user is in the database
# memoize user object
@user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
# handle user not found
rescue ActiveRecord::RecordNotFound => e
# raise custom error
raise(
ExceptionHandler::InvalidToken,
("#{Message.invalid_token} #{e.message}")
)
end
# decode authentication token
def decoded_auth_token
@decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
end
# check for token in `Authorization` header
def http_auth_header
if headers['Authorization'].present?
return headers['Authorization'].split(' ').last
end
raise(ExceptionHandler::MissingToken, Message.missing_token)
end
end
Помощник ExceptionHandler
module ExceptionHandler
extend ActiveSupport::Concern
# Define custom error subclasses - rescue catches `StandardErrors`
class AuthenticationError < StandardError; end
class MissingToken < StandardError; end
class InvalidToken < StandardError; end
included do
# Define custom handlers
rescue_from ActiveRecord::RecordInvalid, with: :four_twenty_two
rescue_from ExceptionHandler::AuthenticationError, with: :unauthorized_request
rescue_from ExceptionHandler::MissingToken, with: :four_twenty_two
rescue_from ExceptionHandler::InvalidToken, with: :four_twenty_two
rescue_from ActiveRecord::RecordNotFound do |e|
json_response({ message: e.message }, :not_found)
end
end
private
# JSON response with message; Status code 422 - unprocessable entity
def four_twenty_two(e)
json_response({ message: e.message }, :unprocessable_entity)
end
# JSON response with message; Status code 401 - Unauthorized
def unauthorized_request(e)
json_response({ message: e.message }, :unauthorized)
end
end